Skip to content

Commit 95ca9d1

Browse files
authored
[xc-admin] Add message buffer instructions (#869)
* [xc-admin] Add message buffer instructions * Use coral-xyz/anchor * Address feedbacks * Address feedbacks
1 parent 183081c commit 95ca9d1

File tree

15 files changed

+472
-13
lines changed

15 files changed

+472
-13
lines changed

governance/xc_admin/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ WORKDIR /home/node/
77
USER 1000
88

99
COPY --chown=1000:1000 governance/xc_admin governance/xc_admin
10+
COPY --chown=1000:1000 pythnet/message_buffer pythnet/message_buffer
1011

1112
RUN npx lerna run build --scope="{crank_executor,crank_pythnet_relayer,proposer_server}" --include-dependencies
1213

governance/xc_admin/packages/xc_admin_common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
},
2222
"dependencies": {
2323
"@certusone/wormhole-sdk": "^0.9.8",
24+
"@coral-xyz/anchor": "^0.26.0",
2425
"@pythnetwork/client": "^2.17.0",
2526
"@solana/buffer-layout": "^4.0.1",
2627
"@solana/web3.js": "^1.73.0",
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { AnchorProvider, Wallet, Program, Idl } from "@coral-xyz/anchor";
2+
import {
3+
getPythClusterApiUrl,
4+
PythCluster,
5+
} from "@pythnetwork/client/lib/cluster";
6+
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
7+
import {
8+
MessageBufferMultisigInstruction,
9+
MESSAGE_BUFFER_PROGRAM_ID,
10+
MultisigInstructionProgram,
11+
MultisigParser,
12+
} from "..";
13+
import messageBuffer from "message_buffer/idl/message_buffer.json";
14+
import { MessageBuffer } from "message_buffer/idl/message_buffer";
15+
16+
test("Message buffer multisig instruction parse: create buffer", (done) => {
17+
jest.setTimeout(60000);
18+
19+
const cluster: PythCluster = "pythtest-crosschain";
20+
21+
const messageBufferProgram = new Program(
22+
messageBuffer as Idl,
23+
new PublicKey(MESSAGE_BUFFER_PROGRAM_ID),
24+
new AnchorProvider(
25+
new Connection(getPythClusterApiUrl(cluster)),
26+
new Wallet(new Keypair()),
27+
AnchorProvider.defaultOptions()
28+
)
29+
) as unknown as Program<MessageBuffer>;
30+
31+
const parser = MultisigParser.fromCluster(cluster);
32+
33+
const allowedProgramAuth = PublicKey.unique();
34+
const baseAccountKey = PublicKey.unique();
35+
36+
messageBufferProgram.methods
37+
.createBuffer(allowedProgramAuth, baseAccountKey, 100)
38+
.accounts({
39+
admin: PublicKey.unique(),
40+
payer: PublicKey.unique(),
41+
})
42+
.remainingAccounts([
43+
{
44+
pubkey: PublicKey.unique(),
45+
isSigner: false,
46+
isWritable: true,
47+
},
48+
])
49+
.instruction()
50+
.then((instruction) => {
51+
const parsedInstruction = parser.parseInstruction(instruction);
52+
53+
if (parsedInstruction instanceof MessageBufferMultisigInstruction) {
54+
expect(parsedInstruction.program).toBe(
55+
MultisigInstructionProgram.MessageBuffer
56+
);
57+
expect(parsedInstruction.name).toBe("createBuffer");
58+
59+
expect(
60+
parsedInstruction.accounts.named["whitelist"].pubkey.equals(
61+
instruction.keys[0].pubkey
62+
)
63+
).toBeTruthy();
64+
expect(parsedInstruction.accounts.named["whitelist"].isSigner).toBe(
65+
instruction.keys[0].isSigner
66+
);
67+
expect(parsedInstruction.accounts.named["whitelist"].isWritable).toBe(
68+
instruction.keys[0].isWritable
69+
);
70+
71+
expect(
72+
parsedInstruction.accounts.named["admin"].pubkey.equals(
73+
instruction.keys[1].pubkey
74+
)
75+
).toBeTruthy();
76+
expect(parsedInstruction.accounts.named["admin"].isSigner).toBe(
77+
instruction.keys[1].isSigner
78+
);
79+
expect(parsedInstruction.accounts.named["admin"].isWritable).toBe(
80+
instruction.keys[1].isWritable
81+
);
82+
83+
expect(
84+
parsedInstruction.accounts.named["payer"].pubkey.equals(
85+
instruction.keys[2].pubkey
86+
)
87+
).toBeTruthy();
88+
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
89+
instruction.keys[2].isSigner
90+
);
91+
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
92+
instruction.keys[2].isWritable
93+
);
94+
95+
expect(
96+
parsedInstruction.accounts.named["systemProgram"].pubkey.equals(
97+
instruction.keys[3].pubkey
98+
)
99+
).toBeTruthy();
100+
expect(parsedInstruction.accounts.named["systemProgram"].isSigner).toBe(
101+
instruction.keys[3].isSigner
102+
);
103+
expect(
104+
parsedInstruction.accounts.named["systemProgram"].isWritable
105+
).toBe(instruction.keys[3].isWritable);
106+
107+
expect(parsedInstruction.accounts.remaining.length).toBe(1);
108+
109+
expect(
110+
parsedInstruction.accounts.remaining[0].pubkey.equals(
111+
instruction.keys[4].pubkey
112+
)
113+
).toBeTruthy();
114+
expect(parsedInstruction.accounts.remaining[0].isSigner).toBe(
115+
instruction.keys[4].isSigner
116+
);
117+
expect(parsedInstruction.accounts.remaining[0].isWritable).toBe(
118+
instruction.keys[4].isWritable
119+
);
120+
121+
expect(
122+
parsedInstruction.args.allowedProgramAuth.equals(allowedProgramAuth)
123+
).toBeTruthy();
124+
expect(
125+
parsedInstruction.args.baseAccountKey.equals(baseAccountKey)
126+
).toBeTruthy();
127+
expect(parsedInstruction.args.targetSize).toBe(100);
128+
129+
done();
130+
} else {
131+
done("Not instance of MessageBufferMultisigInstruction");
132+
}
133+
});
134+
});
135+
136+
test("Message buffer multisig instruction parse: delete buffer", (done) => {
137+
jest.setTimeout(60000);
138+
139+
const cluster: PythCluster = "pythtest-crosschain";
140+
141+
const messageBufferProgram = new Program(
142+
messageBuffer as Idl,
143+
new PublicKey(MESSAGE_BUFFER_PROGRAM_ID),
144+
new AnchorProvider(
145+
new Connection(getPythClusterApiUrl(cluster)),
146+
new Wallet(new Keypair()),
147+
AnchorProvider.defaultOptions()
148+
)
149+
) as unknown as Program<MessageBuffer>;
150+
151+
const parser = MultisigParser.fromCluster(cluster);
152+
153+
const allowedProgramAuth = PublicKey.unique();
154+
const baseAccountKey = PublicKey.unique();
155+
156+
messageBufferProgram.methods
157+
.deleteBuffer(allowedProgramAuth, baseAccountKey)
158+
.accounts({
159+
admin: PublicKey.unique(),
160+
payer: PublicKey.unique(),
161+
messageBuffer: PublicKey.unique(),
162+
})
163+
.instruction()
164+
.then((instruction) => {
165+
const parsedInstruction = parser.parseInstruction(instruction);
166+
167+
if (parsedInstruction instanceof MessageBufferMultisigInstruction) {
168+
expect(parsedInstruction.program).toBe(
169+
MultisigInstructionProgram.MessageBuffer
170+
);
171+
expect(parsedInstruction.name).toBe("deleteBuffer");
172+
173+
expect(
174+
parsedInstruction.accounts.named["whitelist"].pubkey.equals(
175+
instruction.keys[0].pubkey
176+
)
177+
).toBeTruthy();
178+
expect(parsedInstruction.accounts.named["whitelist"].isSigner).toBe(
179+
instruction.keys[0].isSigner
180+
);
181+
expect(parsedInstruction.accounts.named["whitelist"].isWritable).toBe(
182+
instruction.keys[0].isWritable
183+
);
184+
185+
expect(
186+
parsedInstruction.accounts.named["admin"].pubkey.equals(
187+
instruction.keys[1].pubkey
188+
)
189+
).toBeTruthy();
190+
expect(parsedInstruction.accounts.named["admin"].isSigner).toBe(
191+
instruction.keys[1].isSigner
192+
);
193+
expect(parsedInstruction.accounts.named["admin"].isWritable).toBe(
194+
instruction.keys[1].isWritable
195+
);
196+
197+
expect(
198+
parsedInstruction.accounts.named["payer"].pubkey.equals(
199+
instruction.keys[2].pubkey
200+
)
201+
).toBeTruthy();
202+
expect(parsedInstruction.accounts.named["payer"].isSigner).toBe(
203+
instruction.keys[2].isSigner
204+
);
205+
expect(parsedInstruction.accounts.named["payer"].isWritable).toBe(
206+
instruction.keys[2].isWritable
207+
);
208+
209+
expect(
210+
parsedInstruction.accounts.named["messageBuffer"].pubkey.equals(
211+
instruction.keys[3].pubkey
212+
)
213+
).toBeTruthy();
214+
expect(parsedInstruction.accounts.named["messageBuffer"].isSigner).toBe(
215+
instruction.keys[3].isSigner
216+
);
217+
expect(
218+
parsedInstruction.accounts.named["messageBuffer"].isWritable
219+
).toBe(instruction.keys[3].isWritable);
220+
221+
expect(parsedInstruction.accounts.remaining.length).toBe(0);
222+
223+
expect(
224+
parsedInstruction.args.allowedProgramAuth.equals(allowedProgramAuth)
225+
).toBeTruthy();
226+
expect(
227+
parsedInstruction.args.baseAccountKey.equals(baseAccountKey)
228+
).toBeTruthy();
229+
230+
done();
231+
} else {
232+
done("Not instance of MessageBufferMultisigInstruction");
233+
}
234+
});
235+
});

governance/xc_admin/packages/xc_admin_common/src/__tests__/PythMultisigInstruction.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AnchorProvider, Wallet } from "@project-serum/anchor";
1+
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
22
import { pythOracleProgram } from "@pythnetwork/client";
33
import {
44
getPythClusterApiUrl,

governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AnchorProvider, Wallet } from "@project-serum/anchor";
1+
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
22
import { pythOracleProgram } from "@pythnetwork/client";
33
import {
44
getPythClusterApiUrl,

governance/xc_admin/packages/xc_admin_common/src/__tests__/WormholeMultisigInstruction.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createWormholeProgramInterface } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
2-
import { AnchorProvider, Wallet } from "@project-serum/anchor";
2+
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
33
import {
44
getPythClusterApiUrl,
55
PythCluster,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from "./remote_executor";
88
export * from "./bpf_upgradable_loader";
99
export * from "./deterministic_oracle_accounts";
1010
export * from "./cranks";
11+
export * from "./message_buffer";
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { getPythProgramKeyForCluster, PythCluster } from "@pythnetwork/client";
2+
import { PublicKey } from "@solana/web3.js";
3+
4+
/**
5+
* Address of the message buffer program.
6+
*/
7+
export const MESSAGE_BUFFER_PROGRAM_ID: PublicKey = new PublicKey(
8+
"7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM"
9+
);
10+
11+
export const MESSAGE_BUFFER_BUFFER_SIZE = 2048;
12+
13+
export function isMessageBufferAvailable(cluster: PythCluster): boolean {
14+
return cluster === "pythtest-crosschain";
15+
}
16+
17+
export function getPythOracleMessageBufferCpiAuth(
18+
cluster: PythCluster
19+
): PublicKey {
20+
const pythOracleProgramId = getPythProgramKeyForCluster(cluster);
21+
return PublicKey.findProgramAddressSync(
22+
[Buffer.from("upd_price_write"), MESSAGE_BUFFER_PROGRAM_ID.toBuffer()],
23+
pythOracleProgramId
24+
)[0];
25+
}
26+
27+
// TODO: We can remove this when createBuffer takes message buffer account
28+
// as a named account because Anchor can automatically find the address.
29+
export function getMessageBufferAddressForPrice(
30+
cluster: PythCluster,
31+
priceAccount: PublicKey
32+
): PublicKey {
33+
return PublicKey.findProgramAddressSync(
34+
[
35+
getPythOracleMessageBufferCpiAuth(cluster).toBuffer(),
36+
Buffer.from("message"),
37+
priceAccount.toBuffer(),
38+
],
39+
MESSAGE_BUFFER_PROGRAM_ID
40+
)[0];
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
MultisigInstruction,
3+
MultisigInstructionProgram,
4+
UNRECOGNIZED_INSTRUCTION,
5+
} from ".";
6+
import { AnchorAccounts, resolveAccountNames } from "./anchor";
7+
import messageBuffer from "message_buffer/idl/message_buffer.json";
8+
import { TransactionInstruction } from "@solana/web3.js";
9+
import { Idl, BorshCoder } from "@coral-xyz/anchor";
10+
11+
export class MessageBufferMultisigInstruction implements MultisigInstruction {
12+
readonly program = MultisigInstructionProgram.MessageBuffer;
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+
): MessageBufferMultisigInstruction {
30+
const messageBufferInstructionCoder = new BorshCoder(messageBuffer as Idl)
31+
.instruction;
32+
33+
const deserializedData = messageBufferInstructionCoder.decode(
34+
instruction.data
35+
);
36+
37+
if (deserializedData) {
38+
return new MessageBufferMultisigInstruction(
39+
deserializedData.name,
40+
deserializedData.data,
41+
resolveAccountNames(
42+
messageBuffer as Idl,
43+
deserializedData.name,
44+
instruction
45+
)
46+
);
47+
} else {
48+
return new MessageBufferMultisigInstruction(
49+
UNRECOGNIZED_INSTRUCTION,
50+
{ data: instruction.data },
51+
{ named: {}, remaining: instruction.keys }
52+
);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)