[xc-admin] add bpf loader (#1097)
* Add bpf loader * Add close program * Cleanup * Add comment * Show in xc-admin * Remove console log * Fix comment
This commit is contained in:
parent
eb9526675c
commit
f72c0d93ad
|
@ -190,6 +190,42 @@ multisigCommand("upgrade-program", "Upgrade a program from a buffer")
|
|||
await vault.proposeInstructions([proposalInstruction], cluster);
|
||||
});
|
||||
|
||||
multisigCommand(
|
||||
"close-program",
|
||||
"Close a program, retrieve the funds. WARNING : THIS WILL BRICK THE PROGRAM AND THE ACCOUNTS IT OWNS FOREVER"
|
||||
)
|
||||
.requiredOption("-p, --program-id <pubkey>", "program that you want to close")
|
||||
.requiredOption("-s, --spill <pubkey>", "address to receive the funds")
|
||||
.action(async (options: any) => {
|
||||
const vault = await loadVaultFromOptions(options);
|
||||
const spill = new PublicKey(options.spill);
|
||||
const cluster: PythCluster = options.cluster;
|
||||
const programId: PublicKey = new PublicKey(options.programId);
|
||||
|
||||
const programDataAccount = PublicKey.findProgramAddressSync(
|
||||
[programId.toBuffer()],
|
||||
BPF_UPGRADABLE_LOADER
|
||||
)[0];
|
||||
|
||||
const proposalInstruction: TransactionInstruction = {
|
||||
programId: BPF_UPGRADABLE_LOADER,
|
||||
// 4-bytes instruction discriminator, got it from https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html
|
||||
data: Buffer.from([5, 0, 0, 0]),
|
||||
keys: [
|
||||
{ pubkey: programDataAccount, isSigner: false, isWritable: true },
|
||||
{ pubkey: spill, isSigner: false, isWritable: true },
|
||||
{
|
||||
pubkey: await vault.getVaultAuthorityPDA(cluster),
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
{ pubkey: programId, isSigner: false, isWritable: true },
|
||||
],
|
||||
};
|
||||
|
||||
await vault.proposeInstructions([proposalInstruction], cluster);
|
||||
});
|
||||
|
||||
multisigCommand(
|
||||
"init-price",
|
||||
"Init price (useful for changing the exponent), only to be used on unused price feeds"
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import { PythCluster } from "@pythnetwork/client";
|
||||
import {
|
||||
BpfUpgradableLoaderInstruction,
|
||||
MultisigInstructionProgram,
|
||||
MultisigParser,
|
||||
UNRECOGNIZED_INSTRUCTION,
|
||||
} from "../multisig_transaction";
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { BPF_UPGRADABLE_LOADER } from "../bpf_upgradable_loader";
|
||||
|
||||
test("Bpf Upgradable Loader multisig instruction parse", (done) => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
const cluster: PythCluster = "devnet";
|
||||
|
||||
const parser = MultisigParser.fromCluster(cluster);
|
||||
|
||||
const upgradeInstruction = new TransactionInstruction({
|
||||
programId: BPF_UPGRADABLE_LOADER,
|
||||
data: Buffer.from([3, 0, 0, 0]),
|
||||
keys: [
|
||||
{ pubkey: new PublicKey(0), isSigner: false, isWritable: true },
|
||||
{ pubkey: new PublicKey(1), isSigner: false, isWritable: true },
|
||||
{ pubkey: new PublicKey(2), isSigner: false, isWritable: true },
|
||||
{ pubkey: new PublicKey(3), isSigner: false, isWritable: true },
|
||||
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
||||
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
||||
{
|
||||
pubkey: new PublicKey(4),
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: new PublicKey(5),
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const parsedInstruction = parser.parseInstruction(upgradeInstruction);
|
||||
if (parsedInstruction instanceof BpfUpgradableLoaderInstruction) {
|
||||
expect(parsedInstruction.program).toBe(
|
||||
MultisigInstructionProgram.BpfUpgradableLoader
|
||||
);
|
||||
expect(parsedInstruction.name).toBe("Upgrade");
|
||||
expect(
|
||||
parsedInstruction.accounts.named.programData.pubkey.equals(
|
||||
new PublicKey(0)
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.program.pubkey.equals(new PublicKey(1))
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.buffer.pubkey.equals(new PublicKey(2))
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.spill.pubkey.equals(new PublicKey(3))
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.rent.pubkey.equals(SYSVAR_RENT_PUBKEY)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.clock.pubkey.equals(SYSVAR_CLOCK_PUBKEY)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
parsedInstruction.accounts.named.upgradeAuthority.pubkey.equals(
|
||||
new PublicKey(4)
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.accounts.remaining.length).toBe(1);
|
||||
expect(
|
||||
parsedInstruction.accounts.remaining[0].pubkey.equals(new PublicKey(5))
|
||||
).toBeTruthy();
|
||||
expect(parsedInstruction.args).toEqual({});
|
||||
} else {
|
||||
done("Not instance of BpfUpgradableLoaderInstruction");
|
||||
}
|
||||
|
||||
const badInstruction = new TransactionInstruction({
|
||||
keys: [],
|
||||
programId: new PublicKey(BPF_UPGRADABLE_LOADER),
|
||||
data: Buffer.from([9]),
|
||||
});
|
||||
|
||||
const parsedBadInstruction = parser.parseInstruction(badInstruction);
|
||||
if (parsedBadInstruction instanceof BpfUpgradableLoaderInstruction) {
|
||||
expect(parsedBadInstruction.program).toBe(
|
||||
MultisigInstructionProgram.BpfUpgradableLoader
|
||||
);
|
||||
expect(parsedBadInstruction.name).toBe(UNRECOGNIZED_INSTRUCTION);
|
||||
expect(
|
||||
parsedBadInstruction.args.data.equals(Buffer.from([9]))
|
||||
).toBeTruthy();
|
||||
done();
|
||||
} else {
|
||||
done("Not instance of BpfUpgradableLoaderInstruction");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
import { TransactionInstruction } from "@solana/web3.js";
|
||||
import {
|
||||
MultisigInstruction,
|
||||
MultisigInstructionProgram,
|
||||
UNRECOGNIZED_INSTRUCTION,
|
||||
} from ".";
|
||||
import { AnchorAccounts } from "./anchor";
|
||||
import * as BufferLayout from "@solana/buffer-layout";
|
||||
|
||||
// Source: https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html
|
||||
export class BpfUpgradableLoaderInstruction implements MultisigInstruction {
|
||||
readonly program = MultisigInstructionProgram.BpfUpgradableLoader;
|
||||
readonly name: string;
|
||||
readonly args: { [key: string]: any };
|
||||
readonly accounts: AnchorAccounts;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
args: { [key: string]: any },
|
||||
accounts: AnchorAccounts
|
||||
) {
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
static fromTransactionInstruction(
|
||||
instruction: TransactionInstruction
|
||||
): BpfUpgradableLoaderInstruction {
|
||||
try {
|
||||
const instructionTypeLayout = BufferLayout.u32("instruction");
|
||||
const typeIndex = instructionTypeLayout.decode(instruction.data);
|
||||
switch (typeIndex) {
|
||||
case 3:
|
||||
return new BpfUpgradableLoaderInstruction(
|
||||
"Upgrade",
|
||||
{},
|
||||
{
|
||||
named: {
|
||||
programData: instruction.keys[0],
|
||||
program: instruction.keys[1],
|
||||
buffer: instruction.keys[2],
|
||||
spill: instruction.keys[3],
|
||||
rent: instruction.keys[4],
|
||||
clock: instruction.keys[5],
|
||||
upgradeAuthority: instruction.keys[6],
|
||||
},
|
||||
remaining: instruction.keys.slice(7),
|
||||
}
|
||||
);
|
||||
case 4:
|
||||
return new BpfUpgradableLoaderInstruction(
|
||||
"SetAuthority",
|
||||
{},
|
||||
{
|
||||
named: {
|
||||
programData: instruction.keys[0],
|
||||
currentAuthority: instruction.keys[1],
|
||||
newAuthority: instruction.keys[2],
|
||||
},
|
||||
remaining: instruction.keys.slice(3),
|
||||
}
|
||||
);
|
||||
case 5:
|
||||
return new BpfUpgradableLoaderInstruction(
|
||||
"Close",
|
||||
{},
|
||||
{
|
||||
named: {
|
||||
programData: instruction.keys[0],
|
||||
spill: instruction.keys[1],
|
||||
upgradeAuthority: instruction.keys[2],
|
||||
program: instruction.keys[3],
|
||||
},
|
||||
remaining: instruction.keys.slice(4),
|
||||
}
|
||||
);
|
||||
default: // Many more cases are not supported
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
} catch {
|
||||
return new BpfUpgradableLoaderInstruction(
|
||||
UNRECOGNIZED_INSTRUCTION,
|
||||
{ data: instruction.data },
|
||||
{ named: {}, remaining: instruction.keys }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ import { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruc
|
|||
import { PythMultisigInstruction } from "./PythMultisigInstruction";
|
||||
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
|
||||
import { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";
|
||||
import { BpfUpgradableLoaderInstruction } from "./BpfUpgradableLoaderMultisigInstruction";
|
||||
import { BPF_UPGRADABLE_LOADER } from "../bpf_upgradable_loader";
|
||||
|
||||
export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction";
|
||||
export enum MultisigInstructionProgram {
|
||||
|
@ -20,6 +22,7 @@ export enum MultisigInstructionProgram {
|
|||
WormholeBridge,
|
||||
MessageBuffer,
|
||||
SystemProgram,
|
||||
BpfUpgradableLoader,
|
||||
UnrecognizedProgram,
|
||||
}
|
||||
|
||||
|
@ -77,6 +80,10 @@ export class MultisigParser {
|
|||
return SystemProgramMultisigInstruction.fromTransactionInstruction(
|
||||
instruction
|
||||
);
|
||||
} else if (instruction.programId.equals(BPF_UPGRADABLE_LOADER)) {
|
||||
return BpfUpgradableLoaderInstruction.fromTransactionInstruction(
|
||||
instruction
|
||||
);
|
||||
} else {
|
||||
return UnrecognizedProgram.fromTransactionInstruction(instruction);
|
||||
}
|
||||
|
@ -87,3 +94,4 @@ export { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
|
|||
export { PythMultisigInstruction } from "./PythMultisigInstruction";
|
||||
export { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";
|
||||
export { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";
|
||||
export { BpfUpgradableLoaderInstruction } from "./BpfUpgradableLoaderMultisigInstruction";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
AptosAuthorizeUpgradeContract,
|
||||
AuthorizeGovernanceDataSourceTransfer,
|
||||
BpfUpgradableLoaderInstruction,
|
||||
CosmosUpgradeContract,
|
||||
EvmSetWormholeAddress,
|
||||
EvmUpgradeContract,
|
||||
|
@ -95,6 +96,9 @@ export const WormholeInstructionView = ({
|
|||
: parsedInstruction instanceof
|
||||
SystemProgramMultisigInstruction
|
||||
? 'System Program'
|
||||
: parsedInstruction instanceof
|
||||
BpfUpgradableLoaderInstruction
|
||||
? 'BPF Upgradable Loader'
|
||||
: 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -108,7 +112,9 @@ export const WormholeInstructionView = ({
|
|||
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||
parsedInstruction instanceof
|
||||
MessageBufferMultisigInstruction ||
|
||||
parsedInstruction instanceof SystemProgramMultisigInstruction
|
||||
parsedInstruction instanceof
|
||||
SystemProgramMultisigInstruction ||
|
||||
parsedInstruction instanceof BpfUpgradableLoaderInstruction
|
||||
? parsedInstruction.name
|
||||
: 'Unknown'}
|
||||
</div>
|
||||
|
@ -121,8 +127,8 @@ export const WormholeInstructionView = ({
|
|||
{parsedInstruction instanceof PythMultisigInstruction ||
|
||||
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||
parsedInstruction instanceof MessageBufferMultisigInstruction ||
|
||||
parsedInstruction instanceof
|
||||
SystemProgramMultisigInstruction ? (
|
||||
parsedInstruction instanceof SystemProgramMultisigInstruction ||
|
||||
parsedInstruction instanceof BpfUpgradableLoaderInstruction ? (
|
||||
Object.keys(parsedInstruction.args).length > 0 ? (
|
||||
<div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
|
||||
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
|
||||
|
@ -204,7 +210,8 @@ export const WormholeInstructionView = ({
|
|||
{parsedInstruction instanceof PythMultisigInstruction ||
|
||||
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||
parsedInstruction instanceof MessageBufferMultisigInstruction ||
|
||||
parsedInstruction instanceof SystemProgramMultisigInstruction ? (
|
||||
parsedInstruction instanceof SystemProgramMultisigInstruction ||
|
||||
parsedInstruction instanceof BpfUpgradableLoaderInstruction ? (
|
||||
<div
|
||||
key={`${index}_accounts`}
|
||||
className="grid grid-cols-4 justify-between"
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
WormholeMultisigInstruction,
|
||||
getManyProposalsInstructions,
|
||||
SystemProgramMultisigInstruction,
|
||||
BpfUpgradableLoaderInstruction,
|
||||
} from 'xc_admin_common'
|
||||
import { ClusterContext } from '../../contexts/ClusterContext'
|
||||
import { useMultisigContext } from '../../contexts/MultisigContext'
|
||||
|
@ -248,7 +249,11 @@ const Proposal = ({
|
|||
const multisigCluster = getMultisigCluster(contextCluster)
|
||||
const targetClusters: (PythCluster | 'unknown')[] = []
|
||||
instructions.map((ix) => {
|
||||
if (ix instanceof PythMultisigInstruction) {
|
||||
if (
|
||||
ix instanceof PythMultisigInstruction ||
|
||||
ix instanceof SystemProgramMultisigInstruction ||
|
||||
ix instanceof BpfUpgradableLoaderInstruction
|
||||
) {
|
||||
targetClusters.push(multisigCluster)
|
||||
} else if (
|
||||
ix instanceof WormholeMultisigInstruction &&
|
||||
|
@ -319,9 +324,7 @@ const Proposal = ({
|
|||
return (
|
||||
parsedRemoteInstruction instanceof PythMultisigInstruction ||
|
||||
parsedRemoteInstruction instanceof
|
||||
MessageBufferMultisigInstruction ||
|
||||
parsedRemoteInstruction instanceof
|
||||
SystemProgramMultisigInstruction
|
||||
MessageBufferMultisigInstruction
|
||||
)
|
||||
}) &&
|
||||
ix.governanceAction.targetChainId === 'pythnet')
|
||||
|
@ -551,11 +554,17 @@ const Proposal = ({
|
|||
? 'Pyth Oracle'
|
||||
: instruction instanceof WormholeMultisigInstruction
|
||||
? 'Wormhole'
|
||||
: instruction instanceof SystemProgramMultisigInstruction
|
||||
? 'System Program'
|
||||
: instruction instanceof BpfUpgradableLoaderInstruction
|
||||
? 'BPF Upgradable Loader'
|
||||
: 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
{instruction instanceof PythMultisigInstruction ||
|
||||
instruction instanceof WormholeMultisigInstruction ? (
|
||||
instruction instanceof WormholeMultisigInstruction ||
|
||||
instruction instanceof BpfUpgradableLoaderInstruction ||
|
||||
instruction instanceof SystemProgramMultisigInstruction ? (
|
||||
<div
|
||||
key={`${index}_instructionName`}
|
||||
className="flex justify-between"
|
||||
|
@ -583,7 +592,9 @@ const Proposal = ({
|
|||
className="grid grid-cols-4 justify-between"
|
||||
>
|
||||
<div>Arguments</div>
|
||||
{instruction instanceof PythMultisigInstruction ? (
|
||||
{instruction instanceof PythMultisigInstruction ||
|
||||
instruction instanceof SystemProgramMultisigInstruction ||
|
||||
instruction instanceof BpfUpgradableLoaderInstruction ? (
|
||||
Object.keys(instruction.args).length > 0 ? (
|
||||
<div className="col-span-4 mt-2 bg-darkGray2 p-4 lg:col-span-3 lg:mt-0">
|
||||
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
|
||||
|
@ -634,7 +645,9 @@ const Proposal = ({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{instruction instanceof PythMultisigInstruction ? (
|
||||
{instruction instanceof PythMultisigInstruction ||
|
||||
instruction instanceof SystemProgramMultisigInstruction ||
|
||||
instruction instanceof BpfUpgradableLoaderInstruction ? (
|
||||
<div
|
||||
key={`${index}_accounts`}
|
||||
className="grid grid-cols-4 justify-between"
|
||||
|
|
Loading…
Reference in New Issue