Add all required bits for stake instruction decoding (#312)

This commit is contained in:
Pierre 2021-06-25 11:21:00 +10:00 committed by GitHub
parent 32ba45d16b
commit 9fa5c4e4a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 2 deletions

View File

@ -9,6 +9,7 @@ import { useConnection, useSolanaExplorerUrlSuffix } from '../utils/connection';
import { useWallet, useWalletPublicKeys } from '../utils/wallet';
import NewOrder from './instructions/NewOrder';
import UnknownInstruction from './instructions/UnknownInstruction';
import StakeInstruction from '../components/instructions/StakeInstruction';
import SystemInstruction from '../components/instructions/SystemInstruction';
import DexInstruction from '../components/instructions/DexInstruction';
import TokenInstruction from '../components/instructions/TokenInstruction';
@ -215,6 +216,19 @@ export default function SignTransactionFormContent({
onOpenAddress={onOpenAddress}
/>
);
case 'stakeAuthorizeWithSeed':
case 'stakeAuthorize':
case 'stakeDeactivate':
case 'stakeDelegate':
case 'stakeInitialize':
case 'stakeSplit':
case 'stakeWithdraw':
return (
<StakeInstruction
instruction={instruction}
onOpenAddress={onOpenAddress}
/>
);
case 'newOrder':
return (
<NewOrder instruction={instruction} onOpenAddress={onOpenAddress} />

View File

@ -0,0 +1,65 @@
import React from 'react';
import Typography from '@material-ui/core/Typography';
import LabelValue from './LabelValue';
const TYPE_LABELS = {
stakeAuthorizeWithSeed: 'Stake authorize with seed',
stakeAuthorize: 'Stake authorize',
stakeDeactivate: 'Deactivate stake',
stakeDelegate: 'Delegate stake',
stakeInitialize: 'Initialize stake',
stakeSplit: 'Split stake',
stakeWithdraw: 'Withdraw stake',
};
const DATA_LABELS = {
stakePubkey: { label: 'Stake', address: true },
authorizedStaker: { label: 'Authorized staker', address: true },
authorizedWithdrawer: { label: 'Authorized withdrawer', address: true },
lockup: { label: 'Lockup', address: false },
authorizedPubkey: { label: 'Authorized', address: true },
votePubkey: { label: 'Vote', address: true },
authorizedSeed: { label: 'Seed', address: false },
noncePubkey: { label: 'Nonce', address: true },
authorityBase: { label: 'Authority base', address: true },
authoritySeed: { label: 'Authority seed', address: false },
authorityOwner: { label: 'Authority owner', address: true },
newAuthorizedPubkey: { label: 'New authorized', address: true },
stakeAuthorizationType: { label: 'Stake authorization type', address: false, transform: () => JSON.stringify },
custodianPubkey: { label: 'Custodian', address: true },
splitStakePubkey: { label: 'Split to', address: true },
lamports: { label: 'Lamports', address: false },
};
export default function StakeInstruction({ instruction, onOpenAddress }) {
const { type, data } = instruction;
return (
<>
<Typography
variant="subtitle1"
style={{ fontWeight: 'bold' }}
gutterBottom
>
{TYPE_LABELS[type]}
</Typography>
{data &&
Object.entries(data).map(([key, value]) => {
const dataLabel = DATA_LABELS[key];
if (!dataLabel) {
return null;
}
const { label, address, transform } = dataLabel;
return (
<LabelValue
key={key}
label={label + ''}
value={address ? value?.toBase58() : (transform ? transform(value) : value)}
link={address}
onClick={() => address && onOpenAddress(value?.toBase58())}
/>
);
})}
</>
);
}

View File

@ -1,5 +1,5 @@
import bs58 from 'bs58';
import { Message, SystemInstruction, SystemProgram } from '@solana/web3.js';
import { Message, StakeInstruction, StakeProgram, SystemInstruction, SystemProgram } from '@solana/web3.js';
import {
decodeInstruction,
decodeTokenInstructionData,
@ -96,6 +96,9 @@ const toInstruction = async (
if (programId.equals(SystemProgram.programId)) {
console.log('[' + index + '] Handled as system instruction');
return handleSystemInstruction(publicKey, instruction, accountKeys);
} else if (programId.equals(StakeProgram.programId)) {
console.log('[' + index + '] Handled as stake instruction');
return handleStakeInstruction(publicKey, instruction, accountKeys);
} else if (programId.equals(TOKEN_PROGRAM_ID)) {
console.log('[' + index + '] Handled as token instruction');
let decodedInstruction = decodeTokenInstruction(decoded);
@ -155,7 +158,9 @@ const toInstruction = async (
programId,
};
}
} catch {}
} catch (e) {
console.log(`Failed to decode instruction: ${e}`);
}
// all decodings failed
console.log('[' + index + '] Failed, data: ' + JSON.stringify(decoded));
@ -380,6 +385,74 @@ const handleSystemInstruction = (publicKey, instruction, accountKeys) => {
};
};
const handleStakeInstruction = (publicKey, instruction, accountKeys) => {
const { programIdIndex, accounts, data } = instruction;
if (!programIdIndex || !accounts || !data) {
return;
}
// construct stake instruction
const stakeInstruction = {
programId: accountKeys[programIdIndex],
keys: accounts.map((accountIndex) => ({
pubkey: accountKeys[accountIndex],
})),
data: bs58.decode(data),
};
let decoded;
const type = StakeInstruction.decodeInstructionType(stakeInstruction);
switch (type) {
case 'AuthorizeWithSeed':
decoded = StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction);
break;
case 'Authorize':
decoded = StakeInstruction.decodeAuthorize(stakeInstruction);
break;
case 'Deactivate':
decoded = StakeInstruction.decodeDeactivate(stakeInstruction);
break;
case 'Delegate':
decoded = StakeInstruction.decodeDelegate(stakeInstruction);
break;
case 'Initialize':
decoded = StakeInstruction.decodeInitialize(stakeInstruction);
// Lockup inactive if all zeroes
const lockup = decoded.lockup;
if (lockup && lockup.unixTimestamp === 0 && lockup.epoch === 0 && lockup.custodian.equals(PublicKey.default)) {
decoded.lockup = 'Inactive';
}
else {
decoded.lockup = `unixTimestamp: ${lockup.unixTimestamp}, custodian: ${lockup.epoch}, custodian: ${lockup.custodian.toBase58()}`;
}
// flatten authorized to allow address render
decoded.authorizedStaker = decoded.authorized.staker
decoded.authorizedWithdrawer = decoded.authorized.withdrawer
delete decoded.authorized
break;
case 'Split':
decoded = StakeInstruction.decodeSplit(stakeInstruction);
break;
case 'Withdraw':
decoded = StakeInstruction.decodeWithdraw(stakeInstruction);
break;
default:
return;
}
if (
!decoded ||
(decoded.fromPubkey && !publicKey.equals(decoded.fromPubkey))
) {
return;
}
return {
type: 'stake' + type,
data: decoded,
};
};
const handleTokenInstruction = (
publicKey,
accounts,