Skip to content

Commit e339a96

Browse files
authored
solana: Add Docker for generating governance instructions (wormhole-foundation#639)
This PR is based on wormhole-foundation#428. Example usage: 1. `cd solana` 2. Save this file into `solana/claimOwnership.ts` ```ts import { Connection } from "@solana/web3.js"; import { NTT } from "./ts/lib/ntt.ts"; import { OWNER, serializeInstruction } from "./ts/lib/utils.ts"; import { SolanaNtt } from "./ts/sdk/ntt.ts"; (async () => { const connection = new Connection("https://localhost"); const ntt = new SolanaNtt( "Testnet", "Solana", connection, { coreBridge: "INSERT_WORMHOLE_ADDRESS", ntt: { token: "", manager: "INSERT_NTT_ADDRESS", transceiver: {}, }, }, "INSERT_VERSION" ); const ix = await NTT.createClaimOwnershipInstruction(ntt.program, { newOwner: OWNER, }); console.log(serializeInstruction(ix).toString("hex")); })(); ``` 3. `docker build --target governance --tag governance --file Dockerfile ..` 4. `docker run -i --rm governance < claimOwnership.ts`
1 parent 6c5363c commit e339a96

File tree

8 files changed

+173
-31
lines changed

8 files changed

+173
-31
lines changed

Tiltfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ docker_build(
1212
context = "./",
1313
only = ["./sdk", "./solana"],
1414
ignore=["./sdk/__tests__", "./sdk/Dockerfile", "./sdk/ci.yaml", "./sdk/**/dist", "./sdk/node_modules", "./sdk/**/node_modules"],
15+
target = "builder",
1516
dockerfile = "./solana/Dockerfile",
1617
)
1718
docker_build(

solana/Dockerfile

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM backpackapp/build:v0.29.0@sha256:9aee169b2d8b89b4a4243419ae35c176773136e78d751b3e439eff692c9c1293 as anchor
1+
FROM backpackapp/build:v0.29.0@sha256:9aee169b2d8b89b4a4243419ae35c176773136e78d751b3e439eff692c9c1293 AS anchor
22

33
WORKDIR /usr/src/solana/
44

@@ -43,7 +43,7 @@ COPY solana/scripts scripts
4343

4444
RUN make target/idl/example_native_token_transfers.json
4545

46-
FROM scratch as export
46+
FROM scratch AS export
4747

4848
COPY --from=builder /opt/solana/deps /opt/solana/deps
4949
COPY --from=builder /usr/src/solana/ts /usr/src/solana/ts
@@ -52,3 +52,24 @@ COPY --from=builder /usr/src/solana/tsconfig.esm.json /usr/src/solana/tsconfig.e
5252
COPY --from=builder /usr/src/solana/tsconfig.cjs.json /usr/src/solana/tsconfig.cjs.json
5353
COPY --from=builder /usr/src/solana/target/idl /usr/src/solana/target/idl
5454
COPY --from=builder /usr/src/solana/target/types /usr/src/solana/target/types
55+
56+
FROM node:lts-alpine@sha256:41e4389f3d988d2ed55392df4db1420ad048ae53324a8e2b7c6d19508288107e AS governance
57+
58+
WORKDIR /app
59+
60+
# RUN apk add python3
61+
RUN apk add --no-cache make python3 gcc g++
62+
63+
COPY tsconfig.json tsconfig.json
64+
COPY tsconfig.cjs.json tsconfig.cjs.json
65+
COPY tsconfig.esm.json tsconfig.esm.json
66+
COPY package.json package.json
67+
COPY package-lock.json package-lock.json
68+
COPY sdk sdk
69+
RUN npm ci
70+
RUN npm run build
71+
72+
WORKDIR /app/solana
73+
COPY solana/ts ts
74+
75+
ENTRYPOINT [ "npx", "tsx" ]

solana/ts/lib/ntt.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
BN,
32
Program,
43
parseIdlErrors,
54
translateError,
@@ -33,6 +32,7 @@ import {
3332
VAA,
3433
keccak256,
3534
} from "@wormhole-foundation/sdk-definitions";
35+
import BN from "bn.js";
3636

3737
import { Ntt } from "@wormhole-foundation/sdk-definitions-ntt";
3838

@@ -93,6 +93,7 @@ export namespace NTT {
9393
["inbox_item", Ntt.messageDigest(chain, nttMessage)],
9494
programId
9595
);
96+
const upgradeLock = (): PublicKey => derivePda("upgrade_lock", programId);
9697
const outboxRateLimitAccount = (): PublicKey =>
9798
derivePda("outbox_rate_limit", programId);
9899
const tokenAuthority = (): PublicKey =>
@@ -131,6 +132,7 @@ export namespace NTT {
131132
outboxRateLimitAccount,
132133
inboxRateLimitAccount,
133134
inboxItemAccount,
135+
upgradeLock,
134136
sessionAuthority,
135137
tokenAuthority,
136138
pendingTokenAuthority,
@@ -720,19 +722,66 @@ export namespace NTT {
720722
return transferIx;
721723
}
722724

725+
export async function createTransferOwnershipOneStepUncheckedInstruction(
726+
program: Program<NttBindings.NativeTokenTransfer<IdlVersion>>,
727+
args: {
728+
owner: PublicKey;
729+
newOwner: PublicKey;
730+
},
731+
pdas?: Pdas
732+
) {
733+
pdas = pdas ?? NTT.pdas(program.programId);
734+
return program.methods
735+
.transferOwnershipOneStepUnchecked()
736+
.accountsStrict({
737+
config: pdas.configAccount(),
738+
owner: args.owner,
739+
newOwner: args.newOwner,
740+
upgradeLock: pdas.upgradeLock(),
741+
programData: programDataAddress(program.programId),
742+
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
743+
})
744+
.instruction();
745+
}
746+
723747
export async function createTransferOwnershipInstruction(
724748
program: Program<NttBindings.NativeTokenTransfer<IdlVersion>>,
725749
args: {
750+
owner: PublicKey;
726751
newOwner: PublicKey;
727752
},
728753
pdas?: Pdas
729754
) {
730755
pdas = pdas ?? NTT.pdas(program.programId);
731756
return program.methods
732757
.transferOwnership()
733-
.accounts({
758+
.accountsStrict({
734759
config: pdas.configAccount(),
760+
owner: args.owner,
735761
newOwner: args.newOwner,
762+
upgradeLock: pdas.upgradeLock(),
763+
programData: programDataAddress(program.programId),
764+
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
765+
})
766+
.instruction();
767+
}
768+
769+
export async function createClaimOwnershipInstruction(
770+
program: Program<NttBindings.NativeTokenTransfer<IdlVersion>>,
771+
args: {
772+
newOwner: PublicKey;
773+
},
774+
pdas?: Pdas
775+
) {
776+
pdas = pdas ?? NTT.pdas(program.programId);
777+
return program.methods
778+
.claimOwnership()
779+
.accountsStrict({
780+
config: pdas.configAccount(),
781+
newOwner: args.newOwner,
782+
upgradeLock: pdas.upgradeLock(),
783+
programData: programDataAddress(program.programId),
784+
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
736785
})
737786
.instruction();
738787
}
@@ -1035,7 +1084,7 @@ export namespace NTT {
10351084
.instruction();
10361085
}
10371086

1038-
export async function setInboundLimit(
1087+
export async function createSetInboundLimitInstruction(
10391088
program: Program<NttBindings.NativeTokenTransfer<IdlVersion>>,
10401089
args: {
10411090
owner: PublicKey;

solana/ts/lib/utils.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { BN } from "@coral-xyz/anchor";
2-
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
1+
import {
2+
PublicKey,
3+
PublicKeyInitData,
4+
TransactionInstruction,
5+
} from "@solana/web3.js";
36
import {
47
Chain,
58
ChainId,
@@ -8,6 +11,7 @@ import {
811
encoding,
912
toChainId,
1013
} from "@wormhole-foundation/sdk-base";
14+
import BN from "bn.js";
1115

1216
export const BPF_LOADER_UPGRADEABLE_PROGRAM_ID = new PublicKey(
1317
"BPFLoaderUpgradeab1e11111111111111111111111"
@@ -115,3 +119,41 @@ export const quoterAddresses = (programId: PublicKeyInitData) => {
115119
registeredNttAccount,
116120
};
117121
};
122+
123+
// governance utils
124+
125+
export function serializeInstruction(ix: TransactionInstruction): Buffer {
126+
const programId = ix.programId.toBuffer();
127+
const accountsLen = Buffer.alloc(2);
128+
accountsLen.writeUInt16BE(ix.keys.length);
129+
const accounts = Buffer.concat(
130+
ix.keys.map((account) => {
131+
const isSigner = Buffer.alloc(1);
132+
isSigner.writeUInt8(account.isSigner ? 1 : 0);
133+
const isWritable = Buffer.alloc(1);
134+
isWritable.writeUInt8(account.isWritable ? 1 : 0);
135+
const pubkey = account.pubkey.toBuffer();
136+
return Buffer.concat([pubkey, isSigner, isWritable]);
137+
})
138+
);
139+
const dataLen = Buffer.alloc(2);
140+
dataLen.writeUInt16BE(ix.data.length);
141+
return Buffer.concat([programId, accountsLen, accounts, dataLen, ix.data]);
142+
}
143+
144+
export function appendGovernanceHeader(
145+
data: Buffer,
146+
governanceProgramId: PublicKey
147+
): Buffer {
148+
const module = Buffer.from("GeneralPurposeGovernance".padStart(32, "\0"));
149+
const action = Buffer.alloc(1);
150+
action.writeUInt8(2); // SolanaCall
151+
const chainId = Buffer.alloc(2);
152+
chainId.writeUInt16BE(1); // solana
153+
const programId = governanceProgramId.toBuffer();
154+
return Buffer.concat([module, action, chainId, programId, data]);
155+
}
156+
157+
// sentinel values used in governance
158+
export const OWNER = new PublicKey(Buffer.from("owner".padEnd(32, "\0")));
159+
export const PAYER = new PublicKey(Buffer.from("payer".padEnd(32, "\0")));

solana/ts/sdk/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { _platform } from "@wormhole-foundation/sdk-solana";
33
import { SolanaNtt } from "./ntt.js";
44
import { SolanaNttWithExecutor } from "./nttWithExecutor.js";
55
import "@wormhole-foundation/sdk-definitions-ntt";
6+
import "./side-effects";
67

78
registerProtocol(_platform, "Ntt", SolanaNtt);
89
registerProtocol(_platform, "NttWithExecutor", SolanaNttWithExecutor);

solana/ts/sdk/ntt.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
511511
async *setOwner(newOwner: AnySolanaAddress, payer: AccountAddress<C>) {
512512
const sender = new SolanaAddress(payer).unwrap();
513513
const ix = await NTT.createTransferOwnershipInstruction(this.program, {
514+
owner: new SolanaAddress(await this.getOwner()).unwrap(),
514515
newOwner: new SolanaAddress(newOwner).unwrap(),
515516
});
516517

@@ -1144,7 +1145,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
11441145
payer: AccountAddress<C>
11451146
) {
11461147
const sender = new SolanaAddress(payer).unwrap();
1147-
const ix = await NTT.setInboundLimit(this.program, {
1148+
const ix = await NTT.createSetInboundLimitInstruction(this.program, {
11481149
owner: sender,
11491150
chain: fromChain,
11501151
limit: new BN(limit.toString()),

solana/ts/sdk/nttWithExecutor.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import { toChainId, type Network } from "@wormhole-foundation/sdk-base";
1+
import { Program } from "@coral-xyz/anchor";
22
import {
3-
type AccountAddress,
4-
type ChainAddress,
5-
type ChainsConfig,
6-
Contracts,
7-
UnsignedTransaction,
8-
} from "@wormhole-foundation/sdk-definitions";
9-
import { Ntt, NttWithExecutor } from "@wormhole-foundation/sdk-definitions-ntt";
10-
import {
11-
SolanaPlatform,
12-
type SolanaPlatformType,
13-
type SolanaChains,
14-
SolanaAddress,
15-
} from "@wormhole-foundation/sdk-solana";
3+
createAssociatedTokenAccountIdempotentInstruction,
4+
createTransferInstruction,
5+
getAssociatedTokenAddressSync,
6+
} from "@solana/spl-token";
167
import {
178
AddressLookupTableAccount,
189
AddressLookupTableProgram,
@@ -23,22 +14,32 @@ import {
2314
TransactionMessage,
2415
VersionedTransaction,
2516
} from "@solana/web3.js";
26-
import { SolanaNtt } from "./ntt.js";
17+
import { toChainId, type Network } from "@wormhole-foundation/sdk-base";
2718
import {
28-
createAssociatedTokenAccountIdempotentInstruction,
29-
createTransferInstruction,
30-
getAssociatedTokenAddressSync,
31-
} from "@solana/spl-token";
32-
import { BN, Program } from "@coral-xyz/anchor";
19+
Contracts,
20+
UnsignedTransaction,
21+
type AccountAddress,
22+
type ChainAddress,
23+
type ChainsConfig,
24+
} from "@wormhole-foundation/sdk-definitions";
25+
import { Ntt, NttWithExecutor } from "@wormhole-foundation/sdk-definitions-ntt";
3326
import {
34-
ExampleNttWithExecutor,
35-
ExampleNttWithExecutorIdl,
36-
} from "../idl/executor/example_ntt_with_executor.js";
27+
SolanaAddress,
28+
SolanaPlatform,
29+
type SolanaChains,
30+
type SolanaPlatformType,
31+
} from "@wormhole-foundation/sdk-solana";
32+
import BN from "bn.js";
3733
import {
3834
ExampleNttSvmLut,
3935
ExampleNttSvmLutIdl,
4036
} from "../idl/executor/example_ntt_svm_lut.js";
37+
import {
38+
ExampleNttWithExecutor,
39+
ExampleNttWithExecutorIdl,
40+
} from "../idl/executor/example_ntt_with_executor.js";
4141
import { chainToBytes } from "../lib/utils.js";
42+
import { SolanaNtt } from "./ntt.js";
4243

4344
export class SolanaNttWithExecutor<N extends Network, C extends SolanaChains>
4445
implements NttWithExecutor<N, C>

solana/ts/sdk/side-effects.ts

Lines changed: 26 additions & 0 deletions
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+
};

0 commit comments

Comments
 (0)