Skip to content

Commit c468f23

Browse files
committed
solana: add docker for generating governance instructions
1 parent 39d67c0 commit c468f23

File tree

5 files changed

+149
-8
lines changed

5 files changed

+149
-8
lines changed

solana/Dockerfile

+17
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,20 @@ COPY solana/Makefile Makefile
4242
COPY solana/scripts scripts
4343

4444
RUN make target/idl/example_native_token_transfers.json
45+
46+
FROM node:lts-alpine3.19@sha256:ec0c413b1d84f3f7f67ec986ba885930c57b5318d2eb3abc6960ee05d4f2eb28 as governance
47+
48+
WORKDIR /app
49+
50+
# RUN apk add python3
51+
RUN apk add make python3 gcc g++
52+
53+
COPY tsconfig.json tsconfig.json
54+
COPY package.json package.json
55+
COPY package-lock.json package-lock.json
56+
RUN npm ci
57+
58+
COPY idl idl
59+
COPY ts ts
60+
61+
ENTRYPOINT [ "npx", "ts-node", "-T" ]

solana/ts/sdk/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
import "./side-effects";
12
export * from "./ntt";
2-
export * from "./quoter";
3+
export * from "./quoter";

solana/ts/sdk/ntt.ts

+64
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export class NTT {
105105
return this.derivePda('config')
106106
}
107107

108+
upgradeLockAccountAddress(): PublicKey {
109+
return this.derivePda('upgrade_lock')
110+
}
111+
108112
outboxRateLimitAccountAddress(): PublicKey {
109113
return this.derivePda('outbox_rate_limit')
110114
}
@@ -307,6 +311,66 @@ export class NTT {
307311
return outboxItem.publicKey
308312
}
309313

314+
async transferOwnership(args: {
315+
payer: Keypair
316+
owner: Keypair,
317+
newOwner: PublicKey
318+
}) {
319+
const ix = await this.createTransferOwnershipInstruction({
320+
owner: args.owner.publicKey,
321+
newOwner: args.newOwner
322+
})
323+
return await this.sendAndConfirmTransaction(
324+
new Transaction().add(ix),
325+
[args.payer, args.owner]
326+
)
327+
}
328+
329+
async createTransferOwnershipInstruction(args: {
330+
owner: PublicKey;
331+
newOwner: PublicKey;
332+
}) {
333+
return this.program.methods
334+
.transferOwnership()
335+
.accountsStrict({
336+
config: this.configAccountAddress(),
337+
owner: args.owner,
338+
newOwner: args.newOwner,
339+
upgradeLock: this.upgradeLockAccountAddress(),
340+
programData: programDataAddress(this.program.programId),
341+
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
342+
})
343+
.instruction();
344+
}
345+
346+
async claimOwnership(args: {
347+
payer: Keypair
348+
owner: Keypair
349+
}) {
350+
const ix = await this.createClaimOwnershipInstruction({
351+
newOwner: args.owner.publicKey
352+
})
353+
return await this.sendAndConfirmTransaction(
354+
new Transaction().add(ix),
355+
[args.payer, args.owner]
356+
)
357+
}
358+
359+
async createClaimOwnershipInstruction(args: {
360+
newOwner: PublicKey;
361+
}) {
362+
return this.program.methods
363+
.claimOwnership()
364+
.accountsStrict({
365+
config: this.configAccountAddress(),
366+
upgradeLock: this.upgradeLockAccountAddress(),
367+
newOwner: args.newOwner,
368+
programData: programDataAddress(this.program.programId),
369+
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
370+
})
371+
.instruction();
372+
}
373+
310374
/**
311375
* Like `sendAndConfirmTransaction` but parses the anchor error code.
312376
*/

solana/ts/sdk/side-effects.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// <sigh>
2+
// when the native secp256k1 is missing, the eccrypto library decides TO PRINT A MESSAGE TO STDOUT:
3+
// https://github.com/bitchan/eccrypto/blob/a4f4a5f85ef5aa1776dfa1b7801cad808264a19c/index.js#L23
4+
//
5+
// do you use a CLI tool that depends on that library and try to pipe the output
6+
// of the tool into another? tough luck
7+
//
8+
// for lack of a better way to stop this, we patch the console.info function to
9+
// drop that particular message...
10+
// </sigh>
11+
const info = console.info;
12+
console.info = function (x: string) {
13+
if (x !== "secp256k1 unavailable, reverting to browser version") {
14+
info(x);
15+
}
16+
};
17+
18+
const warn = console.warn;
19+
console.warn = function (x: string) {
20+
if (
21+
x !==
22+
"bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)"
23+
) {
24+
warn(x);
25+
}
26+
};

solana/ts/sdk/utils.ts

+40-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
encoding,
55
} from "@wormhole-foundation/sdk-base";
66

7-
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
7+
import { PublicKey, PublicKeyInitData, TransactionInstruction } from "@solana/web3.js";
88
import { BN } from "@coral-xyz/anchor";
99

1010
const CHAIN_ID_BYTE_SIZE = 2;
@@ -39,13 +39,13 @@ export const U64 = {
3939
MAX: new BN((2n**64n - 1n).toString()),
4040
to: (amount: number, unit: number) => {
4141
const ret = new BN(Math.round(amount * unit));
42-
42+
4343
if (ret.isNeg())
4444
throw new Error("Value negative");
45-
45+
4646
if (ret.bitLength() > 64)
47-
throw new Error("Value too large");
48-
47+
throw new Error("Value too large");
48+
4949
return ret;
5050
},
5151
from: (amount: BN, unit: number) => amount.toNumber() / unit,
@@ -57,8 +57,41 @@ export function derivePda(
5757
programId: PublicKeyInitData
5858
) {
5959
const toBytes = (s: string | Uint8Array) => typeof s === "string" ? encoding.bytes.encode(s) : s;
60-
return PublicKey.findProgramAddressSync(
60+
return PublicKey.findProgramAddressSync(
6161
Array.isArray(seeds) ? seeds.map(toBytes) : [toBytes(seeds as Seed)],
6262
new PublicKey(programId),
6363
)[0];
64-
}
64+
}
65+
66+
// governance utils
67+
68+
export function serializeInstruction(ix: TransactionInstruction): Buffer {
69+
const programId = ix.programId.toBuffer();
70+
const accountsLen = Buffer.alloc(2);
71+
accountsLen.writeUInt16BE(ix.keys.length);
72+
const accounts = Buffer.concat(ix.keys.map((account) => {
73+
const isSigner = Buffer.alloc(1);
74+
isSigner.writeUInt8(account.isSigner ? 1 : 0);
75+
const isWritable = Buffer.alloc(1);
76+
isWritable.writeUInt8(account.isWritable ? 1 : 0);
77+
const pubkey = account.pubkey.toBuffer();
78+
return Buffer.concat([pubkey, isSigner, isWritable]);
79+
}))
80+
const dataLen = Buffer.alloc(2);
81+
dataLen.writeUInt16BE(ix.data.length);
82+
return Buffer.concat([programId, accountsLen, accounts, dataLen, ix.data]);
83+
}
84+
85+
export function appendGovernanceHeader(data: Buffer, governanceProgramId: PublicKey): Buffer {
86+
const module = Buffer.from("GeneralPurposeGovernance".padStart(32, "\0"));
87+
const action = Buffer.alloc(1);
88+
action.writeUInt8(2); // SolanaCall
89+
const chainId = Buffer.alloc(2);
90+
chainId.writeUInt16BE(1); // solana
91+
const programId = governanceProgramId.toBuffer();
92+
return Buffer.concat([module, action, chainId, programId, data]);
93+
}
94+
95+
// sentinel values used in governance
96+
export const OWNER = new PublicKey(Buffer.from("owner".padEnd(32, "\0")));
97+
export const PAYER = new PublicKey(Buffer.from("payer".padEnd(32, "\0")));

0 commit comments

Comments
 (0)