Skip to content

Commit f72c0d9

Browse files
authored
[xc-admin] add bpf loader (#1097)
* Add bpf loader * Add close program * Cleanup * Add comment * Show in xc-admin * Remove console log * Fix comment
1 parent eb95266 commit f72c0d9

File tree

6 files changed

+269
-11
lines changed

6 files changed

+269
-11
lines changed

governance/xc_admin/packages/xc_admin_cli/src/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,42 @@ multisigCommand("upgrade-program", "Upgrade a program from a buffer")
190190
await vault.proposeInstructions([proposalInstruction], cluster);
191191
});
192192

193+
multisigCommand(
194+
"close-program",
195+
"Close a program, retrieve the funds. WARNING : THIS WILL BRICK THE PROGRAM AND THE ACCOUNTS IT OWNS FOREVER"
196+
)
197+
.requiredOption("-p, --program-id <pubkey>", "program that you want to close")
198+
.requiredOption("-s, --spill <pubkey>", "address to receive the funds")
199+
.action(async (options: any) => {
200+
const vault = await loadVaultFromOptions(options);
201+
const spill = new PublicKey(options.spill);
202+
const cluster: PythCluster = options.cluster;
203+
const programId: PublicKey = new PublicKey(options.programId);
204+
205+
const programDataAccount = PublicKey.findProgramAddressSync(
206+
[programId.toBuffer()],
207+
BPF_UPGRADABLE_LOADER
208+
)[0];
209+
210+
const proposalInstruction: TransactionInstruction = {
211+
programId: BPF_UPGRADABLE_LOADER,
212+
// 4-bytes instruction discriminator, got it from https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html
213+
data: Buffer.from([5, 0, 0, 0]),
214+
keys: [
215+
{ pubkey: programDataAccount, isSigner: false, isWritable: true },
216+
{ pubkey: spill, isSigner: false, isWritable: true },
217+
{
218+
pubkey: await vault.getVaultAuthorityPDA(cluster),
219+
isSigner: true,
220+
isWritable: false,
221+
},
222+
{ pubkey: programId, isSigner: false, isWritable: true },
223+
],
224+
};
225+
226+
await vault.proposeInstructions([proposalInstruction], cluster);
227+
});
228+
193229
multisigCommand(
194230
"init-price",
195231
"Init price (useful for changing the exponent), only to be used on unused price feeds"
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { PythCluster } from "@pythnetwork/client";
2+
import {
3+
BpfUpgradableLoaderInstruction,
4+
MultisigInstructionProgram,
5+
MultisigParser,
6+
UNRECOGNIZED_INSTRUCTION,
7+
} from "../multisig_transaction";
8+
import {
9+
PublicKey,
10+
SYSVAR_CLOCK_PUBKEY,
11+
SYSVAR_RENT_PUBKEY,
12+
TransactionInstruction,
13+
} from "@solana/web3.js";
14+
import { BPF_UPGRADABLE_LOADER } from "../bpf_upgradable_loader";
15+
16+
test("Bpf Upgradable Loader multisig instruction parse", (done) => {
17+
jest.setTimeout(60000);
18+
19+
const cluster: PythCluster = "devnet";
20+
21+
const parser = MultisigParser.fromCluster(cluster);
22+
23+
const upgradeInstruction = new TransactionInstruction({
24+
programId: BPF_UPGRADABLE_LOADER,
25+
data: Buffer.from([3, 0, 0, 0]),
26+
keys: [
27+
{ pubkey: new PublicKey(0), isSigner: false, isWritable: true },
28+
{ pubkey: new PublicKey(1), isSigner: false, isWritable: true },
29+
{ pubkey: new PublicKey(2), isSigner: false, isWritable: true },
30+
{ pubkey: new PublicKey(3), isSigner: false, isWritable: true },
31+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
32+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
33+
{
34+
pubkey: new PublicKey(4),
35+
isSigner: true,
36+
isWritable: false,
37+
},
38+
{
39+
pubkey: new PublicKey(5),
40+
isSigner: true,
41+
isWritable: false,
42+
},
43+
],
44+
});
45+
46+
const parsedInstruction = parser.parseInstruction(upgradeInstruction);
47+
if (parsedInstruction instanceof BpfUpgradableLoaderInstruction) {
48+
expect(parsedInstruction.program).toBe(
49+
MultisigInstructionProgram.BpfUpgradableLoader
50+
);
51+
expect(parsedInstruction.name).toBe("Upgrade");
52+
expect(
53+
parsedInstruction.accounts.named.programData.pubkey.equals(
54+
new PublicKey(0)
55+
)
56+
).toBeTruthy();
57+
expect(
58+
parsedInstruction.accounts.named.program.pubkey.equals(new PublicKey(1))
59+
).toBeTruthy();
60+
expect(
61+
parsedInstruction.accounts.named.buffer.pubkey.equals(new PublicKey(2))
62+
).toBeTruthy();
63+
expect(
64+
parsedInstruction.accounts.named.spill.pubkey.equals(new PublicKey(3))
65+
).toBeTruthy();
66+
expect(
67+
parsedInstruction.accounts.named.rent.pubkey.equals(SYSVAR_RENT_PUBKEY)
68+
).toBeTruthy();
69+
expect(
70+
parsedInstruction.accounts.named.clock.pubkey.equals(SYSVAR_CLOCK_PUBKEY)
71+
).toBeTruthy();
72+
expect(
73+
parsedInstruction.accounts.named.upgradeAuthority.pubkey.equals(
74+
new PublicKey(4)
75+
)
76+
).toBeTruthy();
77+
expect(parsedInstruction.accounts.remaining.length).toBe(1);
78+
expect(
79+
parsedInstruction.accounts.remaining[0].pubkey.equals(new PublicKey(5))
80+
).toBeTruthy();
81+
expect(parsedInstruction.args).toEqual({});
82+
} else {
83+
done("Not instance of BpfUpgradableLoaderInstruction");
84+
}
85+
86+
const badInstruction = new TransactionInstruction({
87+
keys: [],
88+
programId: new PublicKey(BPF_UPGRADABLE_LOADER),
89+
data: Buffer.from([9]),
90+
});
91+
92+
const parsedBadInstruction = parser.parseInstruction(badInstruction);
93+
if (parsedBadInstruction instanceof BpfUpgradableLoaderInstruction) {
94+
expect(parsedBadInstruction.program).toBe(
95+
MultisigInstructionProgram.BpfUpgradableLoader
96+
);
97+
expect(parsedBadInstruction.name).toBe(UNRECOGNIZED_INSTRUCTION);
98+
expect(
99+
parsedBadInstruction.args.data.equals(Buffer.from([9]))
100+
).toBeTruthy();
101+
done();
102+
} else {
103+
done("Not instance of BpfUpgradableLoaderInstruction");
104+
}
105+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { TransactionInstruction } from "@solana/web3.js";
2+
import {
3+
MultisigInstruction,
4+
MultisigInstructionProgram,
5+
UNRECOGNIZED_INSTRUCTION,
6+
} from ".";
7+
import { AnchorAccounts } from "./anchor";
8+
import * as BufferLayout from "@solana/buffer-layout";
9+
10+
// Source: https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html
11+
export class BpfUpgradableLoaderInstruction implements MultisigInstruction {
12+
readonly program = MultisigInstructionProgram.BpfUpgradableLoader;
13+
readonly name: string;
14+
readonly args: { [key: string]: any };
15+
readonly accounts: AnchorAccounts;
16+
17+
constructor(
18+
name: string,
19+
args: { [key: string]: any },
20+
accounts: AnchorAccounts
21+
) {
22+
this.name = name;
23+
this.args = args;
24+
this.accounts = accounts;
25+
}
26+
27+
static fromTransactionInstruction(
28+
instruction: TransactionInstruction
29+
): BpfUpgradableLoaderInstruction {
30+
try {
31+
const instructionTypeLayout = BufferLayout.u32("instruction");
32+
const typeIndex = instructionTypeLayout.decode(instruction.data);
33+
switch (typeIndex) {
34+
case 3:
35+
return new BpfUpgradableLoaderInstruction(
36+
"Upgrade",
37+
{},
38+
{
39+
named: {
40+
programData: instruction.keys[0],
41+
program: instruction.keys[1],
42+
buffer: instruction.keys[2],
43+
spill: instruction.keys[3],
44+
rent: instruction.keys[4],
45+
clock: instruction.keys[5],
46+
upgradeAuthority: instruction.keys[6],
47+
},
48+
remaining: instruction.keys.slice(7),
49+
}
50+
);
51+
case 4:
52+
return new BpfUpgradableLoaderInstruction(
53+
"SetAuthority",
54+
{},
55+
{
56+
named: {
57+
programData: instruction.keys[0],
58+
currentAuthority: instruction.keys[1],
59+
newAuthority: instruction.keys[2],
60+
},
61+
remaining: instruction.keys.slice(3),
62+
}
63+
);
64+
case 5:
65+
return new BpfUpgradableLoaderInstruction(
66+
"Close",
67+
{},
68+
{
69+
named: {
70+
programData: instruction.keys[0],
71+
spill: instruction.keys[1],
72+
upgradeAuthority: instruction.keys[2],
73+
program: instruction.keys[3],
74+
},
75+
remaining: instruction.keys.slice(4),
76+
}
77+
);
78+
default: // Many more cases are not supported
79+
throw Error("Not implemented");
80+
}
81+
} catch {
82+
return new BpfUpgradableLoaderInstruction(
83+
UNRECOGNIZED_INSTRUCTION,
84+
{ data: instruction.data },
85+
{ named: {}, remaining: instruction.keys }
86+
);
87+
}
88+
}
89+
}

governance/xc_admin/packages/xc_admin_common/src/multisig_transaction/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ import { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruc
1313
import { PythMultisigInstruction } from "./PythMultisigInstruction";
1414
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
1515
import { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";
16+
import { BpfUpgradableLoaderInstruction } from "./BpfUpgradableLoaderMultisigInstruction";
17+
import { BPF_UPGRADABLE_LOADER } from "../bpf_upgradable_loader";
1618

1719
export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction";
1820
export enum MultisigInstructionProgram {
1921
PythOracle,
2022
WormholeBridge,
2123
MessageBuffer,
2224
SystemProgram,
25+
BpfUpgradableLoader,
2326
UnrecognizedProgram,
2427
}
2528

@@ -77,6 +80,10 @@ export class MultisigParser {
7780
return SystemProgramMultisigInstruction.fromTransactionInstruction(
7881
instruction
7982
);
83+
} else if (instruction.programId.equals(BPF_UPGRADABLE_LOADER)) {
84+
return BpfUpgradableLoaderInstruction.fromTransactionInstruction(
85+
instruction
86+
);
8087
} else {
8188
return UnrecognizedProgram.fromTransactionInstruction(instruction);
8289
}
@@ -87,3 +94,4 @@ export { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
8794
export { PythMultisigInstruction } from "./PythMultisigInstruction";
8895
export { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";
8996
export { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";
97+
export { BpfUpgradableLoaderInstruction } from "./BpfUpgradableLoaderMultisigInstruction";

governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/WormholeInstructionView.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
AptosAuthorizeUpgradeContract,
33
AuthorizeGovernanceDataSourceTransfer,
4+
BpfUpgradableLoaderInstruction,
45
CosmosUpgradeContract,
56
EvmSetWormholeAddress,
67
EvmUpgradeContract,
@@ -95,6 +96,9 @@ export const WormholeInstructionView = ({
9596
: parsedInstruction instanceof
9697
SystemProgramMultisigInstruction
9798
? 'System Program'
99+
: parsedInstruction instanceof
100+
BpfUpgradableLoaderInstruction
101+
? 'BPF Upgradable Loader'
98102
: 'Unknown'}
99103
</div>
100104
</div>
@@ -108,7 +112,9 @@ export const WormholeInstructionView = ({
108112
parsedInstruction instanceof WormholeMultisigInstruction ||
109113
parsedInstruction instanceof
110114
MessageBufferMultisigInstruction ||
111-
parsedInstruction instanceof SystemProgramMultisigInstruction
115+
parsedInstruction instanceof
116+
SystemProgramMultisigInstruction ||
117+
parsedInstruction instanceof BpfUpgradableLoaderInstruction
112118
? parsedInstruction.name
113119
: 'Unknown'}
114120
</div>
@@ -121,8 +127,8 @@ export const WormholeInstructionView = ({
121127
{parsedInstruction instanceof PythMultisigInstruction ||
122128
parsedInstruction instanceof WormholeMultisigInstruction ||
123129
parsedInstruction instanceof MessageBufferMultisigInstruction ||
124-
parsedInstruction instanceof
125-
SystemProgramMultisigInstruction ? (
130+
parsedInstruction instanceof SystemProgramMultisigInstruction ||
131+
parsedInstruction instanceof BpfUpgradableLoaderInstruction ? (
126132
Object.keys(parsedInstruction.args).length > 0 ? (
127133
<div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
128134
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
@@ -204,7 +210,8 @@ export const WormholeInstructionView = ({
204210
{parsedInstruction instanceof PythMultisigInstruction ||
205211
parsedInstruction instanceof WormholeMultisigInstruction ||
206212
parsedInstruction instanceof MessageBufferMultisigInstruction ||
207-
parsedInstruction instanceof SystemProgramMultisigInstruction ? (
213+
parsedInstruction instanceof SystemProgramMultisigInstruction ||
214+
parsedInstruction instanceof BpfUpgradableLoaderInstruction ? (
208215
<div
209216
key={`${index}_accounts`}
210217
className="grid grid-cols-4 justify-between"

governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
WormholeMultisigInstruction,
1717
getManyProposalsInstructions,
1818
SystemProgramMultisigInstruction,
19+
BpfUpgradableLoaderInstruction,
1920
} from 'xc_admin_common'
2021
import { ClusterContext } from '../../contexts/ClusterContext'
2122
import { useMultisigContext } from '../../contexts/MultisigContext'
@@ -248,7 +249,11 @@ const Proposal = ({
248249
const multisigCluster = getMultisigCluster(contextCluster)
249250
const targetClusters: (PythCluster | 'unknown')[] = []
250251
instructions.map((ix) => {
251-
if (ix instanceof PythMultisigInstruction) {
252+
if (
253+
ix instanceof PythMultisigInstruction ||
254+
ix instanceof SystemProgramMultisigInstruction ||
255+
ix instanceof BpfUpgradableLoaderInstruction
256+
) {
252257
targetClusters.push(multisigCluster)
253258
} else if (
254259
ix instanceof WormholeMultisigInstruction &&
@@ -319,9 +324,7 @@ const Proposal = ({
319324
return (
320325
parsedRemoteInstruction instanceof PythMultisigInstruction ||
321326
parsedRemoteInstruction instanceof
322-
MessageBufferMultisigInstruction ||
323-
parsedRemoteInstruction instanceof
324-
SystemProgramMultisigInstruction
327+
MessageBufferMultisigInstruction
325328
)
326329
}) &&
327330
ix.governanceAction.targetChainId === 'pythnet')
@@ -551,11 +554,17 @@ const Proposal = ({
551554
? 'Pyth Oracle'
552555
: instruction instanceof WormholeMultisigInstruction
553556
? 'Wormhole'
557+
: instruction instanceof SystemProgramMultisigInstruction
558+
? 'System Program'
559+
: instruction instanceof BpfUpgradableLoaderInstruction
560+
? 'BPF Upgradable Loader'
554561
: 'Unknown'}
555562
</div>
556563
</div>
557564
{instruction instanceof PythMultisigInstruction ||
558-
instruction instanceof WormholeMultisigInstruction ? (
565+
instruction instanceof WormholeMultisigInstruction ||
566+
instruction instanceof BpfUpgradableLoaderInstruction ||
567+
instruction instanceof SystemProgramMultisigInstruction ? (
559568
<div
560569
key={`${index}_instructionName`}
561570
className="flex justify-between"
@@ -583,7 +592,9 @@ const Proposal = ({
583592
className="grid grid-cols-4 justify-between"
584593
>
585594
<div>Arguments</div>
586-
{instruction instanceof PythMultisigInstruction ? (
595+
{instruction instanceof PythMultisigInstruction ||
596+
instruction instanceof SystemProgramMultisigInstruction ||
597+
instruction instanceof BpfUpgradableLoaderInstruction ? (
587598
Object.keys(instruction.args).length > 0 ? (
588599
<div className="col-span-4 mt-2 bg-darkGray2 p-4 lg:col-span-3 lg:mt-0">
589600
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
@@ -634,7 +645,9 @@ const Proposal = ({
634645
)}
635646
</div>
636647
)}
637-
{instruction instanceof PythMultisigInstruction ? (
648+
{instruction instanceof PythMultisigInstruction ||
649+
instruction instanceof SystemProgramMultisigInstruction ||
650+
instruction instanceof BpfUpgradableLoaderInstruction ? (
638651
<div
639652
key={`${index}_accounts`}
640653
className="grid grid-cols-4 justify-between"

0 commit comments

Comments
 (0)