[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:
guibescos 2023-10-13 14:48:20 +02:00 committed by GitHub
parent eb9526675c
commit f72c0d93ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 269 additions and 11 deletions

View File

@ -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"

View File

@ -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");
}
});

View File

@ -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 }
);
}
}
}

View File

@ -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";

View File

@ -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"

View File

@ -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"