explorer: Prettify program logs for transactions page (#21453)
This commit is contained in:
parent
4b67a6900d
commit
7aad6fa6a6
|
@ -0,0 +1,68 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Message, ParsedMessage } from "@solana/web3.js";
|
||||||
|
import { Cluster } from "providers/cluster";
|
||||||
|
import { TableCardBody } from "components/common/TableCardBody";
|
||||||
|
import { programLabel } from "utils/tx";
|
||||||
|
import { InstructionLogs } from "utils/program-logs";
|
||||||
|
|
||||||
|
export function ProgramLogsCardBody({
|
||||||
|
message,
|
||||||
|
logs,
|
||||||
|
cluster,
|
||||||
|
}: {
|
||||||
|
message: Message | ParsedMessage;
|
||||||
|
logs: InstructionLogs[];
|
||||||
|
cluster: Cluster;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<TableCardBody>
|
||||||
|
{message.instructions.map((ix, index) => {
|
||||||
|
let programId;
|
||||||
|
if ("programIdIndex" in ix) {
|
||||||
|
const programAccount = message.accountKeys[ix.programIdIndex];
|
||||||
|
if ("pubkey" in programAccount) {
|
||||||
|
programId = programAccount.pubkey;
|
||||||
|
} else {
|
||||||
|
programId = programAccount;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
programId = ix.programId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const programName =
|
||||||
|
programLabel(programId.toBase58(), cluster) || "Unknown Program";
|
||||||
|
const programLogs: InstructionLogs | undefined = logs[index];
|
||||||
|
|
||||||
|
let badgeColor = "white";
|
||||||
|
if (programLogs) {
|
||||||
|
badgeColor = programLogs.failed ? "warning" : "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className={`badge badge-soft-${badgeColor} mr-2`}>
|
||||||
|
#{index + 1}
|
||||||
|
</span>
|
||||||
|
{programName} Instruction
|
||||||
|
</div>
|
||||||
|
{programLogs && (
|
||||||
|
<div className="d-flex align-items-start flex-column text-monospace p-2 font-size-sm">
|
||||||
|
{programLogs.logs.map((log, key) => {
|
||||||
|
return (
|
||||||
|
<span key={key}>
|
||||||
|
<span className="text-muted">{log.prefix}</span>
|
||||||
|
<span className={`text-${log.style}`}>{log.text}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableCardBody>
|
||||||
|
);
|
||||||
|
}
|
|
@ -152,7 +152,7 @@ export function MangoDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={`Mango: ${title || "Unknown"}`}
|
title={`Mango Program: ${title || "Unknown"}`}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
defaultRaw
|
defaultRaw
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function MemoDetailsCard({
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Memo"
|
title="Memo Program: Memo"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -92,7 +92,7 @@ export function SerumDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={`Serum: ${title || "Unknown"}`}
|
title={`Serum Program: ${title || "Unknown"}`}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
defaultRaw
|
defaultRaw
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {
|
||||||
ParsedInstruction,
|
ParsedInstruction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { InstructionCard } from "./InstructionCard";
|
import { InstructionCard } from "./InstructionCard";
|
||||||
|
import { programLabel } from "utils/tx";
|
||||||
|
import { useCluster } from "providers/cluster";
|
||||||
|
|
||||||
export function UnknownDetailsCard({
|
export function UnknownDetailsCard({
|
||||||
ix,
|
ix,
|
||||||
|
@ -19,12 +21,15 @@ export function UnknownDetailsCard({
|
||||||
innerCards?: JSX.Element[];
|
innerCards?: JSX.Element[];
|
||||||
childIndex?: number;
|
childIndex?: number;
|
||||||
}) {
|
}) {
|
||||||
|
const { cluster } = useCluster();
|
||||||
|
const programName =
|
||||||
|
programLabel(ix.programId.toBase58(), cluster) || "Unknown Program";
|
||||||
return (
|
return (
|
||||||
<InstructionCard
|
<InstructionCard
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Unknown"
|
title={`${programName}: Unknown Instruction`}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
defaultRaw
|
defaultRaw
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function CancelOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Cancel Order"
|
title="Serum Program: Cancel Order"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -15,7 +15,7 @@ export function AddOracleDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: AddOracle"
|
title="Mango Program: AddOracle"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
></InstructionCard>
|
></InstructionCard>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function AddPerpMarketDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: AddPerpMarket"
|
title="Mango Program: AddPerpMarket"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export function AddSpotMarketDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: AddSpotMarket"
|
title="Mango Program: AddSpotMarket"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function CancelPerpOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: CancelPerpOrder"
|
title="Mango Program: CancelPerpOrder"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function CancelSpotOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: CancelSpotOrder"
|
title="Mango Program: CancelSpotOrder"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -52,7 +52,7 @@ export function ChangePerpMarketParamsDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: ChangePerpMarketParams"
|
title="Mango Program: ChangePerpMarketParams"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function ConsumeEventsDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={"Mango: ConsumeEvents"}
|
title={"Mango Program: ConsumeEvents"}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function GenericMngoAccountDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={"Mango: " + title}
|
title={"Mango Program: " + title}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function GenericPerpMngoDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={"Mango: " + title}
|
title={"Mango Program: " + title}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function GenericSpotMngoDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title={"Mango: " + title}
|
title={"Mango Program: " + title}
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export function PlacePerpOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: PlacePerpOrder"
|
title="Mango Program: PlacePerpOrder"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -68,7 +68,7 @@ export function PlaceSpotOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Mango: PlaceSpotOrder"
|
title="Mango Program: PlaceSpotOrder"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function CancelOrderByClientIdDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Cancel Order By Client Id"
|
title="Serum Program: Cancel Order By Client Id"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function CancelOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Cancel Order"
|
title="Serum Program: Cancel Order"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function ConsumeEventsDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Consume Events"
|
title="Serum Program: Consume Events"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function InitializeMarketDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Initialize Market"
|
title="Serum Program: Initialize Market"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function MatchOrdersDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Match Orders"
|
title="Serum Program: Match Orders"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function NewOrderDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: New Order"
|
title="Serum Program: New Order"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function SettleFundsDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Serum: Settle Funds"
|
title="Serum Program: Settle Funds"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function AuthorizeDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Stake Authorize"
|
title="Stake Program: Authorize"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function DeactivateDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Deactivate Stake"
|
title="Stake Program: Deactivate Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function DelegateDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Delegate Stake"
|
title="Stake Program: Delegate Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export function InitializeDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Stake Initialize"
|
title="Stake Program: Initialize Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function MergeDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Stake Merge"
|
title="Stake Program: Merge Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function SplitDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Split Stake"
|
title="Stake Program: Split Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function WithdrawDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Withdraw Stake"
|
title="System Program: Withdraw Stake"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function AllocateDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Allocate Account"
|
title="System Program: Allocate Account"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function AllocateWithSeedDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Allocate Account w/ Seed"
|
title="System Program: Allocate Account w/ Seed"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function AssignDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Assign Account"
|
title="System Program: Assign Account"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function AssignWithSeedDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Assign Account w/ Seed"
|
title="System Program: Assign Account w/ Seed"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function CreateDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Create Account"
|
title="System Program: Create Account"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function CreateWithSeedDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Create Account w/ Seed"
|
title="System Program: Create Account w/ Seed"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function NonceAdvanceDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Advance Nonce"
|
title="System Program: Advance Nonce"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function NonceAuthorizeDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Authorize Nonce"
|
title="System Program: Authorize Nonce"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function NonceInitializeDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Initialize Nonce"
|
title="System Program: Initialize Nonce"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function NonceWithdrawDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Withdraw Nonce"
|
title="System Program: Withdraw Nonce"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function TransferDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Transfer"
|
title="System Program: Transfer"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function TransferWithSeedDetailsCard(props: {
|
||||||
ix={ix}
|
ix={ix}
|
||||||
index={index}
|
index={index}
|
||||||
result={result}
|
result={result}
|
||||||
title="Transfer w/ Seed"
|
title="System Program: Transfer w/ Seed"
|
||||||
innerCards={innerCards}
|
innerCards={innerCards}
|
||||||
childIndex={childIndex}
|
childIndex={childIndex}
|
||||||
>
|
>
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function TokenDetailsCard(props: DetailsProps) {
|
||||||
const parsed = create(props.ix.parsed, ParsedInfo);
|
const parsed = create(props.ix.parsed, ParsedInfo);
|
||||||
const { type: rawType, info } = parsed;
|
const { type: rawType, info } = parsed;
|
||||||
const type = create(rawType, TokenInstructionType);
|
const type = create(rawType, TokenInstructionType);
|
||||||
const title = `Token: ${IX_TITLES[type]}`;
|
const title = `Token Program: ${IX_TITLES[type]}`;
|
||||||
const created = create(info, IX_STRUCTS[type] as any);
|
const created = create(info, IX_STRUCTS[type] as any);
|
||||||
return <TokenInstruction title={title} info={created} {...props} />;
|
return <TokenInstruction title={title} info={created} {...props} />;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,227 +1,33 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { SignatureProps } from "pages/TransactionDetailsPage";
|
import { SignatureProps } from "pages/TransactionDetailsPage";
|
||||||
import { useTransactionDetails } from "providers/transactions";
|
import { useTransactionDetails } from "providers/transactions";
|
||||||
import { TransactionError } from "@solana/web3.js";
|
import { ProgramLogsCardBody } from "components/ProgramLogsCardBody";
|
||||||
|
import { prettyProgramLogs } from "utils/program-logs";
|
||||||
const transactionErrorMessage: Map<string, string> = new Map([
|
import { useCluster } from "providers/cluster";
|
||||||
["AccountInUse", "Account in use"],
|
|
||||||
["AccountLoadedTwice", "Account loaded twice"],
|
|
||||||
[
|
|
||||||
"AccountNotFound",
|
|
||||||
"Attempt to debit an account but found no record of a prior credit.",
|
|
||||||
],
|
|
||||||
["ProgramAccountNotFound", "Attempt to load a program that does not exist"],
|
|
||||||
["InsufficientFundsForFee", "Insufficient funds for fee"],
|
|
||||||
[
|
|
||||||
"InvalidAccountForFee",
|
|
||||||
"This account may not be used to pay transaction fees",
|
|
||||||
],
|
|
||||||
["AlreadyProcessed", "This transaction has already been processed"],
|
|
||||||
["BlockhashNotFound", "Blockhash not found"],
|
|
||||||
["CallChainTooDeep", "Loader call chain is too deep"],
|
|
||||||
[
|
|
||||||
"MissingSignatureForFee",
|
|
||||||
"Transaction requires a fee but has no signature present",
|
|
||||||
],
|
|
||||||
["InvalidAccountIndex", "Transaction contains an invalid account reference"],
|
|
||||||
["SignatureFailure", "Transaction did not pass signature verification"],
|
|
||||||
[
|
|
||||||
"InvalidProgramForExecution",
|
|
||||||
"This program may not be used for executing instructions",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"SanitizeFailure",
|
|
||||||
"Transaction failed to sanitize accounts offsets correctly",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"ClusterMaintenance",
|
|
||||||
"Transactions are currently disabled due to cluster maintenance",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"AccountBorrowOutstanding",
|
|
||||||
"Transaction processing left an account with an outstanding borrowed reference",
|
|
||||||
],
|
|
||||||
["InstructionError", "Error processing Instruction {0}: {1}"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const instructionErrorMessage: Map<string, string> = new Map([
|
|
||||||
["GenericError", "generic instruction error"],
|
|
||||||
["InvalidArgument", "invalid program argument"],
|
|
||||||
["InvalidInstructionData", "invalid instruction data"],
|
|
||||||
["InvalidAccountData", "invalid account data for instruction"],
|
|
||||||
["AccountDataTooSmall", "account data too small for instruction"],
|
|
||||||
["InsufficientFunds", "insufficient funds for instruction"],
|
|
||||||
["IncorrectProgramId", "incorrect program id for instruction"],
|
|
||||||
["MissingRequiredSignature", "missing required signature for instruction"],
|
|
||||||
[
|
|
||||||
"AccountAlreadyInitialized",
|
|
||||||
"instruction requires an uninitialized account",
|
|
||||||
],
|
|
||||||
["UninitializedAccount", "instruction requires an initialized account"],
|
|
||||||
[
|
|
||||||
"UnbalancedInstruction",
|
|
||||||
"sum of account balances before and after instruction do not match",
|
|
||||||
],
|
|
||||||
["ModifiedProgramId", "instruction modified the program id of an account"],
|
|
||||||
[
|
|
||||||
"ExternalAccountLamportSpend",
|
|
||||||
"instruction spent from the balance of an account it does not own",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"ExternalAccountDataModified",
|
|
||||||
"instruction modified data of an account it does not own",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"ReadonlyLamportChange",
|
|
||||||
"instruction changed the balance of a read-only account",
|
|
||||||
],
|
|
||||||
["ReadonlyDataModified", "instruction modified data of a read-only account"],
|
|
||||||
["DuplicateAccountIndex", "instruction contains duplicate accounts"],
|
|
||||||
["ExecutableModified", "instruction changed executable bit of an account"],
|
|
||||||
["RentEpochModified", "instruction modified rent epoch of an account"],
|
|
||||||
["NotEnoughAccountKeys", "insufficient account keys for instruction"],
|
|
||||||
["AccountDataSizeChanged", "non-system instruction changed account size"],
|
|
||||||
["AccountNotExecutable", "instruction expected an executable account"],
|
|
||||||
[
|
|
||||||
"AccountBorrowFailed",
|
|
||||||
"instruction tries to borrow reference for an account which is already borrowed",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"AccountBorrowOutstanding",
|
|
||||||
"instruction left account with an outstanding borrowed reference",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"DuplicateAccountOutOfSync",
|
|
||||||
"instruction modifications of multiply-passed account differ",
|
|
||||||
],
|
|
||||||
["Custom", "custom program error: {0}"],
|
|
||||||
["InvalidError", "program returned invalid error code"],
|
|
||||||
["ExecutableDataModified", "instruction changed executable accounts data"],
|
|
||||||
[
|
|
||||||
"ExecutableLamportChange",
|
|
||||||
"instruction changed the balance of a executable account",
|
|
||||||
],
|
|
||||||
["ExecutableAccountNotRentExempt", "executable accounts must be rent exempt"],
|
|
||||||
["UnsupportedProgramId", "Unsupported program id"],
|
|
||||||
["CallDepth", "Cross-program invocation call depth too deep"],
|
|
||||||
["MissingAccount", "An account required by the instruction is missing"],
|
|
||||||
[
|
|
||||||
"ReentrancyNotAllowed",
|
|
||||||
"Cross-program invocation reentrancy not allowed for this instruction",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"MaxSeedLengthExceeded",
|
|
||||||
"Length of the seed is too long for address generation",
|
|
||||||
],
|
|
||||||
["InvalidSeeds", "Provided seeds do not result in a valid address"],
|
|
||||||
["InvalidRealloc", "Failed to reallocate account data"],
|
|
||||||
["ComputationalBudgetExceeded", "Computational budget exceeded"],
|
|
||||||
[
|
|
||||||
"PrivilegeEscalation",
|
|
||||||
"Cross-program invocation with unauthorized signer or writable account",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"ProgramEnvironmentSetupFailure",
|
|
||||||
"Failed to create program execution environment",
|
|
||||||
],
|
|
||||||
["ProgramFailedToComplete", "Program failed to complete"],
|
|
||||||
["ProgramFailedToCompile", "Program failed to compile"],
|
|
||||||
["Immutable", "Account is immutable"],
|
|
||||||
["IncorrectAuthority", "Incorrect authority provided"],
|
|
||||||
["BorshIoError", "Failed to serialize or deserialize account data: {0}"],
|
|
||||||
[
|
|
||||||
"AccountNotRentExempt",
|
|
||||||
"An account does not have enough lamports to be rent-exempt",
|
|
||||||
],
|
|
||||||
["InvalidAccountOwner", "Invalid account owner"],
|
|
||||||
["ArithmeticOverflow", "Program arithmetic overflowed"],
|
|
||||||
["UnsupportedSysvar", "Unsupported sysvar"],
|
|
||||||
["IllegalOwner", "Provided owner is not allowed"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
function getTransactionError(
|
|
||||||
error?: TransactionError | null
|
|
||||||
): string | undefined {
|
|
||||||
if (!error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof error === "string") {
|
|
||||||
const message = transactionErrorMessage.get(error);
|
|
||||||
if (message) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
} else if ("InstructionError" in error) {
|
|
||||||
const out = transactionErrorMessage.get("InstructionError");
|
|
||||||
const innerError = error["InstructionError"];
|
|
||||||
const index = innerError[0];
|
|
||||||
const instructionError = innerError[1];
|
|
||||||
|
|
||||||
if (out) {
|
|
||||||
return out
|
|
||||||
.replace("{0}", index)
|
|
||||||
.replace("{1}", getInstructionError(instructionError));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown transaction error";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstructionError(error: TransactionError): string {
|
|
||||||
let out;
|
|
||||||
let value;
|
|
||||||
|
|
||||||
if (typeof error === "string") {
|
|
||||||
const message = instructionErrorMessage.get(error);
|
|
||||||
if (message) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
} else if ("Custom" in error) {
|
|
||||||
out = instructionErrorMessage.get("Custom");
|
|
||||||
value = error["Custom"][0];
|
|
||||||
} else if ("BorshIoError" in error) {
|
|
||||||
out = instructionErrorMessage.get("BorshIoError");
|
|
||||||
value = error["BorshIoError"][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out && value) {
|
|
||||||
return out.replace("{0}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown instruction error";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ProgramLogSection({ signature }: SignatureProps) {
|
export function ProgramLogSection({ signature }: SignatureProps) {
|
||||||
|
const { cluster } = useCluster();
|
||||||
const details = useTransactionDetails(signature);
|
const details = useTransactionDetails(signature);
|
||||||
const logMessages = details?.data?.transaction?.meta?.logMessages;
|
|
||||||
|
|
||||||
const transactionError = getTransactionError(
|
const transaction = details?.data?.transaction;
|
||||||
details?.data?.transaction?.meta?.err
|
if (!transaction) return null;
|
||||||
);
|
const message = transaction.transaction.message;
|
||||||
|
|
||||||
if ((!logMessages || logMessages.length < 1) && !transactionError) {
|
const logMessages = transaction.meta?.logMessages || null;
|
||||||
return null;
|
const err = transaction.meta?.err || null;
|
||||||
}
|
const prettyLogs = prettyProgramLogs(logMessages, err, cluster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container">
|
|
||||||
<div className="header">
|
|
||||||
<div className="header-body">
|
|
||||||
<h3 className="card-header-title">Program Log</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<ul className="log-messages">
|
<div className="card-header">
|
||||||
{logMessages &&
|
<h3 className="card-header-title">Program Logs</h3>
|
||||||
logMessages.map((message, key) => (
|
</div>
|
||||||
<li key={key}>{message.replace(/^Program log: /, "")}</li>
|
<ProgramLogsCardBody
|
||||||
))}
|
message={message}
|
||||||
{transactionError && (
|
logs={prettyLogs}
|
||||||
<li className="mt-3">Transaction failed: {transactionError}</li>
|
cluster={cluster}
|
||||||
)}
|
/>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,19 +2,8 @@ import React from "react";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import { Connection, Message, Transaction } from "@solana/web3.js";
|
import { Connection, Message, Transaction } from "@solana/web3.js";
|
||||||
import { useCluster } from "providers/cluster";
|
import { useCluster } from "providers/cluster";
|
||||||
import { TableCardBody } from "components/common/TableCardBody";
|
import { InstructionLogs, prettyProgramLogs } from "utils/program-logs";
|
||||||
import { programLabel } from "utils/tx";
|
import { ProgramLogsCardBody } from "components/ProgramLogsCardBody";
|
||||||
|
|
||||||
type LogMessage = {
|
|
||||||
text: string;
|
|
||||||
prefix: string;
|
|
||||||
style: "muted" | "info" | "success" | "warning";
|
|
||||||
};
|
|
||||||
|
|
||||||
type InstructionLogs = {
|
|
||||||
logs: LogMessage[];
|
|
||||||
failed: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_SIGNATURE = bs58.encode(Buffer.alloc(64).fill(0));
|
const DEFAULT_SIGNATURE = bs58.encode(Buffer.alloc(64).fill(0));
|
||||||
|
|
||||||
|
@ -78,46 +67,7 @@ export function SimulatorCard({ message }: { message: Message }) {
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<TableCardBody>
|
<ProgramLogsCardBody message={message} logs={logs} cluster={cluster} />
|
||||||
{message.instructions.map((ix, index) => {
|
|
||||||
const programId = message.accountKeys[ix.programIdIndex];
|
|
||||||
const programName =
|
|
||||||
programLabel(programId.toBase58(), cluster) || "Unknown";
|
|
||||||
const programLogs: InstructionLogs | undefined = logs[index];
|
|
||||||
|
|
||||||
let badgeColor = "white";
|
|
||||||
if (programLogs) {
|
|
||||||
badgeColor = programLogs.failed ? "warning" : "success";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<span className={`badge badge-soft-${badgeColor} mr-2`}>
|
|
||||||
#{index + 1}
|
|
||||||
</span>
|
|
||||||
{programName} Instruction
|
|
||||||
</div>
|
|
||||||
{programLogs && (
|
|
||||||
<div className="d-flex align-items-start flex-column text-monospace p-2 font-size-sm">
|
|
||||||
{programLogs.logs.map((log, key) => {
|
|
||||||
return (
|
|
||||||
<span key={key}>
|
|
||||||
<span className="text-muted">{log.prefix}</span>
|
|
||||||
<span className={`text-${log.style}`}>
|
|
||||||
{log.text}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableCardBody>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -152,124 +102,8 @@ function useSimulator(message: Message) {
|
||||||
// Simulate without signers to skip signer verification
|
// Simulate without signers to skip signer verification
|
||||||
const resp = await connection.simulateTransaction(tx);
|
const resp = await connection.simulateTransaction(tx);
|
||||||
|
|
||||||
let depth = 0;
|
// Prettify logs
|
||||||
let instructionLogs: InstructionLogs[] = [];
|
setLogs(prettyProgramLogs(resp.value.logs, resp.value.err, cluster));
|
||||||
const prefixBuilder = (depth: number) => {
|
|
||||||
const prefix = new Array(depth - 1).fill("\u00A0\u00A0").join("");
|
|
||||||
return prefix + "> ";
|
|
||||||
};
|
|
||||||
|
|
||||||
let instructionError;
|
|
||||||
const responseLogs = resp.value.logs;
|
|
||||||
const responseErr = resp.value.err;
|
|
||||||
if (!responseLogs) {
|
|
||||||
if (resp.value.err) throw new Error(JSON.stringify(responseErr));
|
|
||||||
throw new Error("No logs detected");
|
|
||||||
} else if (responseErr) {
|
|
||||||
if (typeof responseErr !== "string") {
|
|
||||||
let ixError = (responseErr as any)["InstructionError"];
|
|
||||||
const [index, message] = ixError;
|
|
||||||
if (typeof message === "string") {
|
|
||||||
instructionError = { index, message };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(responseErr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
responseLogs.forEach((log) => {
|
|
||||||
if (log.startsWith("Program log:")) {
|
|
||||||
instructionLogs[instructionLogs.length - 1].logs.push({
|
|
||||||
prefix: prefixBuilder(depth),
|
|
||||||
text: log,
|
|
||||||
style: "muted",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const regex = /Program (\w*) invoke \[(\d)\]/g;
|
|
||||||
const matches = [...log.matchAll(regex)];
|
|
||||||
|
|
||||||
if (matches.length > 0) {
|
|
||||||
const programAddress = matches[0][1];
|
|
||||||
const programName =
|
|
||||||
programLabel(programAddress, cluster) ||
|
|
||||||
`Unknown (${programAddress}) Program`;
|
|
||||||
|
|
||||||
if (depth === 0) {
|
|
||||||
instructionLogs.push({
|
|
||||||
logs: [],
|
|
||||||
failed: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
instructionLogs[instructionLogs.length - 1].logs.push({
|
|
||||||
prefix: prefixBuilder(depth),
|
|
||||||
style: "info",
|
|
||||||
text: `Invoking ${programName}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
depth++;
|
|
||||||
} else if (log.includes("success")) {
|
|
||||||
instructionLogs[instructionLogs.length - 1].logs.push({
|
|
||||||
prefix: prefixBuilder(depth),
|
|
||||||
style: "success",
|
|
||||||
text: `Program returned success`,
|
|
||||||
});
|
|
||||||
depth--;
|
|
||||||
} else if (log.includes("failed")) {
|
|
||||||
const instructionLog =
|
|
||||||
instructionLogs[instructionLogs.length - 1];
|
|
||||||
if (!instructionLog.failed) {
|
|
||||||
instructionLog.failed = true;
|
|
||||||
instructionLog.logs.push({
|
|
||||||
prefix: prefixBuilder(depth),
|
|
||||||
style: "warning",
|
|
||||||
text: `Program returned error: ${log.slice(
|
|
||||||
log.indexOf(": ") + 2
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
depth--;
|
|
||||||
} else {
|
|
||||||
if (depth === 0) {
|
|
||||||
instructionLogs.push({
|
|
||||||
logs: [],
|
|
||||||
failed: false,
|
|
||||||
});
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
// system transactions don't start with "Program log:"
|
|
||||||
instructionLogs[instructionLogs.length - 1].logs.push({
|
|
||||||
prefix: prefixBuilder(depth),
|
|
||||||
text: log,
|
|
||||||
style: "muted",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the instruction's simulation returned an error without any logs then add an empty log entry for Runtime error
|
|
||||||
// For example BpfUpgradableLoader fails without returning any logs for Upgrade instruction with buffer that doesn't exist
|
|
||||||
if (instructionError && instructionLogs.length === 0) {
|
|
||||||
instructionLogs.push({
|
|
||||||
logs: [],
|
|
||||||
failed: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
instructionError &&
|
|
||||||
instructionError.index === instructionLogs.length - 1
|
|
||||||
) {
|
|
||||||
const failedIx = instructionLogs[instructionError.index];
|
|
||||||
failedIx.failed = true;
|
|
||||||
failedIx.logs.push({
|
|
||||||
prefix: prefixBuilder(1),
|
|
||||||
text: `Runtime error: ${instructionError.message}`,
|
|
||||||
style: "warning",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setLogs(instructionLogs);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setLogs(null);
|
setLogs(null);
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
import { TransactionError } from "@solana/web3.js";
|
||||||
|
|
||||||
|
const instructionErrorMessage: Map<string, string> = new Map([
|
||||||
|
["GenericError", "generic instruction error"],
|
||||||
|
["InvalidArgument", "invalid program argument"],
|
||||||
|
["InvalidInstructionData", "invalid instruction data"],
|
||||||
|
["InvalidAccountData", "invalid account data for instruction"],
|
||||||
|
["AccountDataTooSmall", "account data too small for instruction"],
|
||||||
|
["InsufficientFunds", "insufficient funds for instruction"],
|
||||||
|
["IncorrectProgramId", "incorrect program id for instruction"],
|
||||||
|
["MissingRequiredSignature", "missing required signature for instruction"],
|
||||||
|
[
|
||||||
|
"AccountAlreadyInitialized",
|
||||||
|
"instruction requires an uninitialized account",
|
||||||
|
],
|
||||||
|
["UninitializedAccount", "instruction requires an initialized account"],
|
||||||
|
[
|
||||||
|
"UnbalancedInstruction",
|
||||||
|
"sum of account balances before and after instruction do not match",
|
||||||
|
],
|
||||||
|
["ModifiedProgramId", "instruction modified the program id of an account"],
|
||||||
|
[
|
||||||
|
"ExternalAccountLamportSpend",
|
||||||
|
"instruction spent from the balance of an account it does not own",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ExternalAccountDataModified",
|
||||||
|
"instruction modified data of an account it does not own",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ReadonlyLamportChange",
|
||||||
|
"instruction changed the balance of a read-only account",
|
||||||
|
],
|
||||||
|
["ReadonlyDataModified", "instruction modified data of a read-only account"],
|
||||||
|
["DuplicateAccountIndex", "instruction contains duplicate accounts"],
|
||||||
|
["ExecutableModified", "instruction changed executable bit of an account"],
|
||||||
|
["RentEpochModified", "instruction modified rent epoch of an account"],
|
||||||
|
["NotEnoughAccountKeys", "insufficient account keys for instruction"],
|
||||||
|
["AccountDataSizeChanged", "non-system instruction changed account size"],
|
||||||
|
["AccountNotExecutable", "instruction expected an executable account"],
|
||||||
|
[
|
||||||
|
"AccountBorrowFailed",
|
||||||
|
"instruction tries to borrow reference for an account which is already borrowed",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"AccountBorrowOutstanding",
|
||||||
|
"instruction left account with an outstanding borrowed reference",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"DuplicateAccountOutOfSync",
|
||||||
|
"instruction modifications of multiply-passed account differ",
|
||||||
|
],
|
||||||
|
["Custom", "custom program error: {0}"],
|
||||||
|
["InvalidError", "program returned invalid error code"],
|
||||||
|
["ExecutableDataModified", "instruction changed executable accounts data"],
|
||||||
|
[
|
||||||
|
"ExecutableLamportChange",
|
||||||
|
"instruction changed the balance of a executable account",
|
||||||
|
],
|
||||||
|
["ExecutableAccountNotRentExempt", "executable accounts must be rent exempt"],
|
||||||
|
["UnsupportedProgramId", "Unsupported program id"],
|
||||||
|
["CallDepth", "Cross-program invocation call depth too deep"],
|
||||||
|
["MissingAccount", "An account required by the instruction is missing"],
|
||||||
|
[
|
||||||
|
"ReentrancyNotAllowed",
|
||||||
|
"Cross-program invocation reentrancy not allowed for this instruction",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"MaxSeedLengthExceeded",
|
||||||
|
"Length of the seed is too long for address generation",
|
||||||
|
],
|
||||||
|
["InvalidSeeds", "Provided seeds do not result in a valid address"],
|
||||||
|
["InvalidRealloc", "Failed to reallocate account data"],
|
||||||
|
["ComputationalBudgetExceeded", "Computational budget exceeded"],
|
||||||
|
[
|
||||||
|
"PrivilegeEscalation",
|
||||||
|
"Cross-program invocation with unauthorized signer or writable account",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ProgramEnvironmentSetupFailure",
|
||||||
|
"Failed to create program execution environment",
|
||||||
|
],
|
||||||
|
["ProgramFailedToComplete", "Program failed to complete"],
|
||||||
|
["ProgramFailedToCompile", "Program failed to compile"],
|
||||||
|
["Immutable", "Account is immutable"],
|
||||||
|
["IncorrectAuthority", "Incorrect authority provided"],
|
||||||
|
["BorshIoError", "Failed to serialize or deserialize account data: {0}"],
|
||||||
|
[
|
||||||
|
"AccountNotRentExempt",
|
||||||
|
"An account does not have enough lamports to be rent-exempt",
|
||||||
|
],
|
||||||
|
["InvalidAccountOwner", "Invalid account owner"],
|
||||||
|
["ArithmeticOverflow", "Program arithmetic overflowed"],
|
||||||
|
["UnsupportedSysvar", "Unsupported sysvar"],
|
||||||
|
["IllegalOwner", "Provided owner is not allowed"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type ProgramError = {
|
||||||
|
index: number;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getTransactionInstructionError(
|
||||||
|
error?: TransactionError | null
|
||||||
|
): ProgramError | undefined {
|
||||||
|
if (!error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === "object" && "InstructionError" in error) {
|
||||||
|
const innerError = error["InstructionError"];
|
||||||
|
const index = innerError[0] as number;
|
||||||
|
const instructionError = innerError[1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
message: getInstructionError(instructionError),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstructionError(error: any): string {
|
||||||
|
let out;
|
||||||
|
let value;
|
||||||
|
|
||||||
|
if (typeof error === "string") {
|
||||||
|
const message = instructionErrorMessage.get(error);
|
||||||
|
if (message) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
} else if ("Custom" in error) {
|
||||||
|
out = instructionErrorMessage.get("Custom");
|
||||||
|
value = error["Custom"];
|
||||||
|
} else if ("BorshIoError" in error) {
|
||||||
|
out = instructionErrorMessage.get("BorshIoError");
|
||||||
|
value = error["BorshIoError"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out && value) {
|
||||||
|
return out.replace("{0}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown instruction error";
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { TransactionError } from "@solana/web3.js";
|
||||||
|
import { Cluster } from "providers/cluster";
|
||||||
|
import { programLabel } from "utils/tx";
|
||||||
|
import { getTransactionInstructionError } from "utils/program-err";
|
||||||
|
|
||||||
|
export type LogMessage = {
|
||||||
|
text: string;
|
||||||
|
prefix: string;
|
||||||
|
style: "muted" | "info" | "success" | "warning";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InstructionLogs = {
|
||||||
|
logs: LogMessage[];
|
||||||
|
failed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function prettyProgramLogs(
|
||||||
|
logs: string[] | null,
|
||||||
|
error: TransactionError | null,
|
||||||
|
cluster: Cluster
|
||||||
|
): InstructionLogs[] {
|
||||||
|
let depth = 0;
|
||||||
|
let prettyLogs: InstructionLogs[] = [];
|
||||||
|
const prefixBuilder = (depth: number) => {
|
||||||
|
const prefix = new Array(depth - 1).fill("\u00A0\u00A0").join("");
|
||||||
|
return prefix + "> ";
|
||||||
|
};
|
||||||
|
|
||||||
|
let prettyError;
|
||||||
|
if (!logs) {
|
||||||
|
if (error) throw new Error(JSON.stringify(error));
|
||||||
|
throw new Error("No logs detected");
|
||||||
|
} else if (error) {
|
||||||
|
prettyError = getTransactionInstructionError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.forEach((log) => {
|
||||||
|
if (log.startsWith("Program log:")) {
|
||||||
|
prettyLogs[prettyLogs.length - 1].logs.push({
|
||||||
|
prefix: prefixBuilder(depth),
|
||||||
|
text: log,
|
||||||
|
style: "muted",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const regex = /Program (\w*) invoke \[(\d)\]/g;
|
||||||
|
const matches = [...log.matchAll(regex)];
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
const programAddress = matches[0][1];
|
||||||
|
const programName =
|
||||||
|
programLabel(programAddress, cluster) ||
|
||||||
|
`Unknown (${programAddress}) Program`;
|
||||||
|
|
||||||
|
if (depth === 0) {
|
||||||
|
prettyLogs.push({
|
||||||
|
logs: [],
|
||||||
|
failed: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
prettyLogs[prettyLogs.length - 1].logs.push({
|
||||||
|
prefix: prefixBuilder(depth),
|
||||||
|
style: "info",
|
||||||
|
text: `Invoking ${programName}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
depth++;
|
||||||
|
} else if (log.includes("success")) {
|
||||||
|
prettyLogs[prettyLogs.length - 1].logs.push({
|
||||||
|
prefix: prefixBuilder(depth),
|
||||||
|
style: "success",
|
||||||
|
text: `Program returned success`,
|
||||||
|
});
|
||||||
|
depth--;
|
||||||
|
} else if (log.includes("failed")) {
|
||||||
|
const instructionLog = prettyLogs[prettyLogs.length - 1];
|
||||||
|
if (!instructionLog.failed) {
|
||||||
|
instructionLog.failed = true;
|
||||||
|
instructionLog.logs.push({
|
||||||
|
prefix: prefixBuilder(depth),
|
||||||
|
style: "warning",
|
||||||
|
text: `Program returned error: ${log.slice(log.indexOf(": ") + 2)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
depth--;
|
||||||
|
} else {
|
||||||
|
if (depth === 0) {
|
||||||
|
prettyLogs.push({
|
||||||
|
logs: [],
|
||||||
|
failed: false,
|
||||||
|
});
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
// system transactions don't start with "Program log:"
|
||||||
|
prettyLogs[prettyLogs.length - 1].logs.push({
|
||||||
|
prefix: prefixBuilder(depth),
|
||||||
|
text: log,
|
||||||
|
style: "muted",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the instruction's simulation returned an error without any logs then add an empty log entry for Runtime error
|
||||||
|
// For example BpfUpgradableLoader fails without returning any logs for Upgrade instruction with buffer that doesn't exist
|
||||||
|
if (prettyError && prettyLogs.length === 0) {
|
||||||
|
prettyLogs.push({
|
||||||
|
logs: [],
|
||||||
|
failed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prettyError && prettyError.index === prettyLogs.length - 1) {
|
||||||
|
const failedIx = prettyLogs[prettyError.index];
|
||||||
|
failedIx.failed = true;
|
||||||
|
failedIx.logs.push({
|
||||||
|
prefix: prefixBuilder(1),
|
||||||
|
text: `Runtime error: ${prettyError.message}`,
|
||||||
|
style: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return prettyLogs;
|
||||||
|
}
|
Loading…
Reference in New Issue