[xc admin] Pyth parser (#477)

* Pyth parser

* Bump pyth-client-js
This commit is contained in:
guibescos 2023-01-12 11:53:20 -06:00 committed by GitHub
parent 8ef49e6128
commit 31ab162168
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 226 additions and 19 deletions

View File

@ -3748,9 +3748,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@pythnetwork/client": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.9.0.tgz",
"integrity": "sha512-2CyDmTwPWW+JCQgRKUpwMR/31oiLWH6I3GA0h2ZXIcbt/hWxcr5TXyKlWuyi+l+jh73WWh88gq8NXLoIgRcvkA==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.10.0.tgz",
"integrity": "sha512-dmj8dAj8K7rhSpaDUIjLGKYs+64kM1LR/V1ht/IShg6Zu0GNJY522+0K0EBcmbjzs3GaHua873DlcvQJlU5iHw==",
"dependencies": {
"buffer": "^6.0.1"
},
@ -12759,7 +12759,7 @@
"license": "ISC",
"dependencies": {
"@certusone/wormhole-sdk": "^0.9.8",
"@pythnetwork/client": "^2.9.0",
"@pythnetwork/client": "^2.10.0",
"@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0",
"@sqds/mesh": "^1.0.6",
@ -15331,9 +15331,9 @@
"version": "1.1.0"
},
"@pythnetwork/client": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.9.0.tgz",
"integrity": "sha512-2CyDmTwPWW+JCQgRKUpwMR/31oiLWH6I3GA0h2ZXIcbt/hWxcr5TXyKlWuyi+l+jh73WWh88gq8NXLoIgRcvkA==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/client/-/client-2.10.0.tgz",
"integrity": "sha512-dmj8dAj8K7rhSpaDUIjLGKYs+64kM1LR/V1ht/IShg6Zu0GNJY522+0K0EBcmbjzs3GaHua873DlcvQJlU5iHw==",
"requires": {
"buffer": "^6.0.1"
},
@ -21192,7 +21192,7 @@
"version": "file:packages/xc-admin-common",
"requires": {
"@certusone/wormhole-sdk": "^0.9.8",
"@pythnetwork/client": "*",
"@pythnetwork/client": "^2.10.0",
"@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0",
"@sqds/mesh": "^1.0.6",

View File

@ -20,7 +20,7 @@
},
"dependencies": {
"@certusone/wormhole-sdk": "^0.9.8",
"@pythnetwork/client": "^2.9.0",
"@pythnetwork/client": "^2.10.0",
"@solana/buffer-layout": "^4.0.1",
"@solana/web3.js": "^1.73.0",
"@sqds/mesh": "^1.0.6",

View File

@ -0,0 +1,167 @@
import { AnchorProvider, Wallet } from "@project-serum/anchor";
import { pythOracleProgram } from "@pythnetwork/client";
import {
getPythClusterApiUrl,
getPythProgramKeyForCluster,
PythCluster,
} from "@pythnetwork/client/lib/cluster";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { MultisigInstructionProgram, MultisigParser } from "..";
import { PythMultisigInstruction } from "../multisig_transaction/PythMultisigInstruction";
test("Pyth multisig instruction parse: create price account", (done) => {
jest.setTimeout(60000);
const cluster: PythCluster = "devnet";
const pythProgram = pythOracleProgram(
getPythProgramKeyForCluster(cluster),
new AnchorProvider(
new Connection(getPythClusterApiUrl(cluster)),
new Wallet(new Keypair()),
AnchorProvider.defaultOptions()
)
);
const parser = MultisigParser.fromCluster(cluster);
pythProgram.methods
.addPrice(-8, 1)
.accounts({
fundingAccount: PublicKey.unique(),
productAccount: PublicKey.unique(),
priceAccount: PublicKey.unique(),
})
.instruction()
.then((instruction) => {
const parsedInstruction = parser.parseInstruction(instruction);
if (parsedInstruction instanceof PythMultisigInstruction) {
expect(parsedInstruction.program).toBe(
MultisigInstructionProgram.PythOracle
);
expect(parsedInstruction.name).toBe("addPrice");
expect(
parsedInstruction.accounts.named["fundingAccount"].pubkey.equals(
instruction.keys[0].pubkey
)
).toBeTruthy();
expect(
parsedInstruction.accounts.named["fundingAccount"].isSigner
).toBe(instruction.keys[0].isSigner);
expect(
parsedInstruction.accounts.named["fundingAccount"].isWritable
).toBe(instruction.keys[0].isWritable);
console.log(parsedInstruction.accounts.named["productAccount"]);
expect(
parsedInstruction.accounts.named["productAccount"].pubkey.equals(
instruction.keys[1].pubkey
)
).toBeTruthy();
expect(
parsedInstruction.accounts.named["productAccount"].isSigner
).toBe(instruction.keys[1].isSigner);
expect(
parsedInstruction.accounts.named["productAccount"].isWritable
).toBe(instruction.keys[1].isWritable);
expect(
parsedInstruction.accounts.named["priceAccount"].pubkey.equals(
instruction.keys[2].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["priceAccount"].isSigner).toBe(
instruction.keys[2].isSigner
);
expect(
parsedInstruction.accounts.named["priceAccount"].isWritable
).toBe(instruction.keys[2].isWritable);
expect(
parsedInstruction.accounts.named["permissionsAccount"].pubkey.equals(
instruction.keys[3].pubkey
)
).toBeTruthy();
expect(
parsedInstruction.accounts.named["permissionsAccount"].isSigner
).toBe(instruction.keys[3].isSigner);
expect(
parsedInstruction.accounts.named["permissionsAccount"].isWritable
).toBe(instruction.keys[3].isWritable);
expect(parsedInstruction.accounts.remaining.length).toBe(0);
expect(parsedInstruction.args.expo).toBe(-8);
expect(parsedInstruction.args.pType).toBe(1);
done();
} else {
done("Not instance of PythMultisigInstruction");
}
});
});
test("Pyth multisig instruction parse: set minimum publishers", (done) => {
jest.setTimeout(60000);
const cluster: PythCluster = "devnet";
const pythProgram = pythOracleProgram(
getPythProgramKeyForCluster(cluster),
new AnchorProvider(
new Connection(getPythClusterApiUrl(cluster)),
new Wallet(new Keypair()),
AnchorProvider.defaultOptions()
)
);
const parser = MultisigParser.fromCluster(cluster);
pythProgram.methods
.setMinPub(25, [0, 0, 0])
.accounts({
fundingAccount: PublicKey.unique(),
priceAccount: PublicKey.unique(),
})
.instruction()
.then((instruction) => {
const parsedInstruction = parser.parseInstruction(instruction);
if (parsedInstruction instanceof PythMultisigInstruction) {
expect(parsedInstruction.program).toBe(
MultisigInstructionProgram.PythOracle
);
expect(parsedInstruction.name).toBe("setMinPub");
expect(
parsedInstruction.accounts.named["fundingAccount"].pubkey.equals(
instruction.keys[0].pubkey
)
).toBeTruthy();
expect(
parsedInstruction.accounts.named["fundingAccount"].isSigner
).toBe(instruction.keys[0].isSigner);
expect(
parsedInstruction.accounts.named["fundingAccount"].isWritable
).toBe(instruction.keys[0].isWritable);
expect(
parsedInstruction.accounts.named["priceAccount"].pubkey.equals(
instruction.keys[1].pubkey
)
).toBeTruthy();
expect(parsedInstruction.accounts.named["priceAccount"].isSigner).toBe(
instruction.keys[1].isSigner
);
expect(
parsedInstruction.accounts.named["priceAccount"].isWritable
).toBe(instruction.keys[1].isWritable);
expect(
parsedInstruction.accounts.named["permissionsAccount"].pubkey.equals(
instruction.keys[2].pubkey
)
).toBeTruthy();
expect(
parsedInstruction.accounts.named["permissionsAccount"].isSigner
).toBe(instruction.keys[2].isSigner);
expect(
parsedInstruction.accounts.named["permissionsAccount"].isWritable
).toBe(instruction.keys[2].isWritable);
expect(parsedInstruction.accounts.remaining.length).toBe(0);
expect(parsedInstruction.args.minPub).toBe(25);
done();
} else {
done("Not instance of PythMultisigInstruction");
}
});
});

View File

@ -46,9 +46,6 @@ test("Wormhole multisig instruction parse: send message without governance paylo
.instruction()
.then((instruction) => {
const parsedInstruction = parser.parseInstruction(instruction);
expect(
parsedInstruction instanceof WormholeMultisigInstruction
).toBeTruthy();
if (parsedInstruction instanceof WormholeMultisigInstruction) {
expect(parsedInstruction.program).toBe(
MultisigInstructionProgram.WormholeBridge
@ -161,7 +158,7 @@ test("Wormhole multisig instruction parse: send message without governance paylo
expect(parsedInstruction.args.targetChain).toBeUndefined();
done();
} else {
done("Not instance of WormholeInstruction");
done("Not instance of WormholeMultisigInstruction");
}
});
});
@ -354,7 +351,7 @@ test("Wormhole multisig instruction parse: send message with governance payload"
done("Not instance of ExecutePostedVaa");
}
} else {
done("Not instance of WormholeInstruction");
done("Not instance of WormholeMultisigInstruction");
}
});
});

View File

@ -0,0 +1,44 @@
import { MultisigInstruction, MultisigInstructionProgram } from ".";
import { AnchorAccounts, resolveAccountNames } from "./anchor";
import { pythIdl, pythOracleCoder } from "@pythnetwork/client";
import { TransactionInstruction } from "@solana/web3.js";
import { Idl } from "@coral-xyz/anchor";
export class PythMultisigInstruction implements MultisigInstruction {
readonly program = MultisigInstructionProgram.PythOracle;
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
): PythMultisigInstruction {
const pythInstructionCoder = pythOracleCoder().instruction;
const deserializedData = pythInstructionCoder.decode(instruction.data);
if (deserializedData) {
return new PythMultisigInstruction(
deserializedData.name,
deserializedData.data,
resolveAccountNames(pythIdl as Idl, deserializedData.name, instruction)
);
} else {
return new PythMultisigInstruction(
"Unrecognized instruction",
{},
{ named: {}, remaining: instruction.keys }
);
}
}
}

View File

@ -4,6 +4,7 @@ import {
} from "@pythnetwork/client/lib/cluster";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import { WORMHOLE_ADDRESS } from "../wormhole";
import { PythMultisigInstruction } from "./PythMultisigInstruction";
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
export enum MultisigInstructionProgram {
@ -30,11 +31,6 @@ export class UnrecognizedProgram implements MultisigInstruction {
return new UnrecognizedProgram(instruction);
}
}
export class PythMultisigInstruction implements MultisigInstruction {
readonly program = MultisigInstructionProgram.PythOracle;
}
export class MultisigParser {
readonly pythOracleAddress: PublicKey;
readonly wormholeBridgeAddress: PublicKey | undefined;
@ -61,6 +57,8 @@ export class MultisigParser {
return WormholeMultisigInstruction.fromTransactionInstruction(
instruction
);
} else if (instruction.programId.equals(this.pythOracleAddress)) {
return PythMultisigInstruction.fromTransactionInstruction(instruction);
} else {
return UnrecognizedProgram.fromTransactionInstruction(instruction);
}

View File

@ -6,5 +6,6 @@ export const WORMHOLE_ADDRESS: Record<PythCluster, PublicKey | undefined> = {
pythtest: new PublicKey("EUrRARh92Cdc54xrDn6qzaqjA77NRrCcfbr8kPwoTL4z"),
devnet: new PublicKey("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5"),
pythnet: new PublicKey("H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU"),
localnet: new PublicKey("gMYYig2utAxVoXnM9UhtTWrt8e7x2SVBZqsWZJeT5Gw"),
testnet: undefined,
};