Skip to content

Commit bb2f1c2

Browse files
ts: verified build utility (otter-sec#1371)
1 parent a01904a commit bb2f1c2

5 files changed

Lines changed: 150 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ incremented for features.
1717
* lang: `Context` now has a new `bumps: BTree<String, u8>` argument, mapping account name to bump seed "found" by the accounts context. This allows one to access bump seeds without having to pass them in from the client or recalculate them in the handler ([#1367](calculated )).
1818
* ts: Remove error logging in the event parser when log websocket encounters a program error ([#1313](https://github.com/project-serum/anchor/pull/1313)).
1919
* ts: Add new `methods` namespace to the program client, introducing a more ergonomic builder API ([#1324](https://github.com/project-serum/anchor/pull/1324)).
20+
* ts: Add registry utility for fetching the latest verified build ([#1371](https://github.com/project-serum/anchor/pull/1371)).
2021

2122
### Breaking
2223

ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@
3333
"test": "jest tests --detectOpenHandles"
3434
},
3535
"dependencies": {
36-
"@project-serum/borsh": "^0.2.2",
36+
"@project-serum/borsh": "^0.2.4",
3737
"@solana/web3.js": "^1.17.0",
3838
"base64-js": "^1.5.1",
3939
"bn.js": "^5.1.2",
4040
"bs58": "^4.0.1",
4141
"buffer-layout": "^1.2.2",
4242
"camelcase": "^5.3.1",
43+
"cross-fetch": "^3.1.5",
4344
"crypto-hash": "^1.3.0",
4445
"eventemitter3": "^4.0.7",
4546
"find": "^0.3.0",

ts/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * as publicKey from "./pubkey.js";
44
export * as bytes from "./bytes/index.js";
55
export * as token from "./token.js";
66
export * as features from "./features.js";
7+
export * as registry from "./registry.js";

ts/src/utils/registry.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import BN from "bn.js";
2+
import fetch from "cross-fetch";
3+
import * as borsh from "@project-serum/borsh";
4+
import { Connection, PublicKey } from "@solana/web3.js";
5+
6+
/**
7+
* Returns a verified build from the anchor registry. null if no such
8+
* verified build exists, e.g., if the program has been upgraded since the
9+
* last verified build.
10+
*/
11+
export async function verifiedBuild(
12+
connection: Connection,
13+
programId: PublicKey,
14+
limit: number = 5
15+
): Promise<Build | null> {
16+
const url = `https://anchor.projectserum.com/api/v0/program/${programId.toString()}/latest?limit=${limit}`;
17+
const [programData, latestBuildsResp] = await Promise.all([
18+
fetchData(connection, programId),
19+
fetch(url),
20+
]);
21+
22+
// Filter out all non successful builds.
23+
const latestBuilds = (await latestBuildsResp.json()).filter(
24+
(b: Build) => !b.aborted && b.state === "Built" && b.verified === "Verified"
25+
);
26+
if (latestBuilds.length === 0) {
27+
return null;
28+
}
29+
30+
// Get the latest build.
31+
const build = latestBuilds[0];
32+
33+
// Has the program been upgraded since the last build?
34+
if (programData.slot.toNumber() !== build.verified_slot) {
35+
return null;
36+
}
37+
38+
// Success.
39+
return build;
40+
}
41+
42+
/**
43+
* Returns the program data account for this program, containing the
44+
* metadata for this program, e.g., the upgrade authority.
45+
*/
46+
export async function fetchData(
47+
connection: Connection,
48+
programId: PublicKey
49+
): Promise<ProgramData> {
50+
const accountInfo = await connection.getAccountInfo(programId);
51+
if (accountInfo === null) {
52+
throw new Error("program account not found");
53+
}
54+
const { program } = decodeUpgradeableLoaderState(accountInfo.data);
55+
const programdataAccountInfo = await connection.getAccountInfo(
56+
program.programdataAddress
57+
);
58+
if (programdataAccountInfo === null) {
59+
throw new Error("program data account not found");
60+
}
61+
const { programData } = decodeUpgradeableLoaderState(
62+
programdataAccountInfo.data
63+
);
64+
return programData;
65+
}
66+
67+
const UPGRADEABLE_LOADER_STATE_LAYOUT = borsh.rustEnum(
68+
[
69+
borsh.struct([], "uninitialized"),
70+
borsh.struct(
71+
[borsh.option(borsh.publicKey(), "authorityAddress")],
72+
"buffer"
73+
),
74+
borsh.struct([borsh.publicKey("programdataAddress")], "program"),
75+
borsh.struct(
76+
[
77+
borsh.u64("slot"),
78+
borsh.option(borsh.publicKey(), "upgradeAuthorityAddress"),
79+
],
80+
"programData"
81+
),
82+
],
83+
undefined,
84+
borsh.u32()
85+
);
86+
87+
export function decodeUpgradeableLoaderState(data: Buffer): any {
88+
return UPGRADEABLE_LOADER_STATE_LAYOUT.decode(data);
89+
}
90+
91+
export type ProgramData = {
92+
slot: BN;
93+
upgradeAuthorityAddress: PublicKey | null;
94+
};
95+
96+
export type Build = {
97+
aborted: boolean;
98+
address: string;
99+
created_at: string;
100+
updated_at: string;
101+
descriptor: string[];
102+
docker: string;
103+
id: number;
104+
name: string;
105+
sha256: string;
106+
upgrade_authority: string;
107+
verified: string;
108+
verified_slot: number;
109+
state: string;
110+
};

ts/yarn.lock

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -855,10 +855,10 @@
855855
"@nodelib/fs.scandir" "2.1.4"
856856
fastq "^1.6.0"
857857

858-
"@project-serum/borsh@^0.2.2":
859-
version "0.2.2"
860-
resolved "https://registry.npmjs.org/@project-serum/borsh/-/borsh-0.2.2.tgz"
861-
integrity sha512-Ms+aWmGVW6bWd3b0+MWwoaYig2QD0F90h0uhr7AzY3dpCb5e2S6RsRW02vFTfa085pY2VLB7nTZNbFECQ1liTg==
858+
"@project-serum/borsh@^0.2.4":
859+
version "0.2.4"
860+
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.4.tgz#8884c3a759984a39d54bf5b7390bd1ee0b579f16"
861+
integrity sha512-tQPc1ktAp1Jtn9D72DmObAfhAic9ivfYBOS5b+T4H7MvkQ84uML88LY1LfvGep30mCy+ua5rf+X9ocPfg6u9MA==
862862
dependencies:
863863
bn.js "^5.1.2"
864864
buffer-layout "^1.2.0"
@@ -1836,6 +1836,13 @@ create-require@^1.1.0:
18361836
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
18371837
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
18381838

1839+
cross-fetch@^3.1.5:
1840+
version "3.1.5"
1841+
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
1842+
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
1843+
dependencies:
1844+
node-fetch "2.6.7"
1845+
18391846
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
18401847
version "7.0.3"
18411848
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@@ -3744,6 +3751,13 @@ node-addon-api@^2.0.0:
37443751
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz"
37453752
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
37463753

3754+
node-fetch@2.6.7:
3755+
version "2.6.7"
3756+
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
3757+
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
3758+
dependencies:
3759+
whatwg-url "^5.0.0"
3760+
37473761
node-fetch@^2.6.1:
37483762
version "2.6.1"
37493763
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz"
@@ -4695,6 +4709,11 @@ tr46@^2.1.0:
46954709
dependencies:
46964710
punycode "^2.1.1"
46974711

4712+
tr46@~0.0.3:
4713+
version "0.0.3"
4714+
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
4715+
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
4716+
46984717
traverse-chain@~0.1.0:
46994718
version "0.1.0"
47004719
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@@ -4929,6 +4948,11 @@ walker@^1.0.7:
49294948
dependencies:
49304949
makeerror "1.0.x"
49314950

4951+
webidl-conversions@^3.0.0:
4952+
version "3.0.1"
4953+
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
4954+
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
4955+
49324956
webidl-conversions@^5.0.0:
49334957
version "5.0.0"
49344958
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz"
@@ -4951,6 +4975,14 @@ whatwg-mimetype@^2.3.0:
49514975
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
49524976
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
49534977

4978+
whatwg-url@^5.0.0:
4979+
version "5.0.0"
4980+
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
4981+
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
4982+
dependencies:
4983+
tr46 "~0.0.3"
4984+
webidl-conversions "^3.0.0"
4985+
49544986
whatwg-url@^8.0.0:
49554987
version "8.4.0"
49564988
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz"

0 commit comments

Comments
 (0)