From c468f236385f1153726cbb44fd58562892646138 Mon Sep 17 00:00:00 2001 From: Csongor Kiss Date: Tue, 23 Apr 2024 23:32:49 +0100 Subject: [PATCH 1/3] solana: add docker for generating governance instructions --- solana/Dockerfile | 17 ++++++++++ solana/ts/sdk/index.ts | 3 +- solana/ts/sdk/ntt.ts | 64 +++++++++++++++++++++++++++++++++++ solana/ts/sdk/side-effects.ts | 26 ++++++++++++++ solana/ts/sdk/utils.ts | 47 +++++++++++++++++++++---- 5 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 solana/ts/sdk/side-effects.ts diff --git a/solana/Dockerfile b/solana/Dockerfile index e4aed9261..731b410c7 100644 --- a/solana/Dockerfile +++ b/solana/Dockerfile @@ -42,3 +42,20 @@ COPY solana/Makefile Makefile COPY solana/scripts scripts RUN make target/idl/example_native_token_transfers.json + +FROM node:lts-alpine3.19@sha256:ec0c413b1d84f3f7f67ec986ba885930c57b5318d2eb3abc6960ee05d4f2eb28 as governance + +WORKDIR /app + +# RUN apk add python3 +RUN apk add make python3 gcc g++ + +COPY tsconfig.json tsconfig.json +COPY package.json package.json +COPY package-lock.json package-lock.json +RUN npm ci + +COPY idl idl +COPY ts ts + +ENTRYPOINT [ "npx", "ts-node", "-T" ] diff --git a/solana/ts/sdk/index.ts b/solana/ts/sdk/index.ts index 2986a2d41..fecc1ee49 100644 --- a/solana/ts/sdk/index.ts +++ b/solana/ts/sdk/index.ts @@ -1,2 +1,3 @@ +import "./side-effects"; export * from "./ntt"; -export * from "./quoter"; \ No newline at end of file +export * from "./quoter"; diff --git a/solana/ts/sdk/ntt.ts b/solana/ts/sdk/ntt.ts index 0f1b32f48..300e8f50f 100644 --- a/solana/ts/sdk/ntt.ts +++ b/solana/ts/sdk/ntt.ts @@ -105,6 +105,10 @@ export class NTT { return this.derivePda('config') } + upgradeLockAccountAddress(): PublicKey { + return this.derivePda('upgrade_lock') + } + outboxRateLimitAccountAddress(): PublicKey { return this.derivePda('outbox_rate_limit') } @@ -307,6 +311,66 @@ export class NTT { return outboxItem.publicKey } + async transferOwnership(args: { + payer: Keypair + owner: Keypair, + newOwner: PublicKey + }) { + const ix = await this.createTransferOwnershipInstruction({ + owner: args.owner.publicKey, + newOwner: args.newOwner + }) + return await this.sendAndConfirmTransaction( + new Transaction().add(ix), + [args.payer, args.owner] + ) + } + + async createTransferOwnershipInstruction(args: { + owner: PublicKey; + newOwner: PublicKey; + }) { + return this.program.methods + .transferOwnership() + .accountsStrict({ + config: this.configAccountAddress(), + owner: args.owner, + newOwner: args.newOwner, + upgradeLock: this.upgradeLockAccountAddress(), + programData: programDataAddress(this.program.programId), + bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID, + }) + .instruction(); + } + + async claimOwnership(args: { + payer: Keypair + owner: Keypair + }) { + const ix = await this.createClaimOwnershipInstruction({ + newOwner: args.owner.publicKey + }) + return await this.sendAndConfirmTransaction( + new Transaction().add(ix), + [args.payer, args.owner] + ) + } + + async createClaimOwnershipInstruction(args: { + newOwner: PublicKey; + }) { + return this.program.methods + .claimOwnership() + .accountsStrict({ + config: this.configAccountAddress(), + upgradeLock: this.upgradeLockAccountAddress(), + newOwner: args.newOwner, + programData: programDataAddress(this.program.programId), + bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID, + }) + .instruction(); + } + /** * Like `sendAndConfirmTransaction` but parses the anchor error code. */ diff --git a/solana/ts/sdk/side-effects.ts b/solana/ts/sdk/side-effects.ts new file mode 100644 index 000000000..c30f57902 --- /dev/null +++ b/solana/ts/sdk/side-effects.ts @@ -0,0 +1,26 @@ +// +// when the native secp256k1 is missing, the eccrypto library decides TO PRINT A MESSAGE TO STDOUT: +// https://github.com/bitchan/eccrypto/blob/a4f4a5f85ef5aa1776dfa1b7801cad808264a19c/index.js#L23 +// +// do you use a CLI tool that depends on that library and try to pipe the output +// of the tool into another? tough luck +// +// for lack of a better way to stop this, we patch the console.info function to +// drop that particular message... +// +const info = console.info; +console.info = function (x: string) { + if (x !== "secp256k1 unavailable, reverting to browser version") { + info(x); + } +}; + +const warn = console.warn; +console.warn = function (x: string) { + if ( + x !== + "bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)" + ) { + warn(x); + } +}; diff --git a/solana/ts/sdk/utils.ts b/solana/ts/sdk/utils.ts index 759be3b25..c41e7f636 100644 --- a/solana/ts/sdk/utils.ts +++ b/solana/ts/sdk/utils.ts @@ -4,7 +4,7 @@ import { encoding, } from "@wormhole-foundation/sdk-base"; -import { PublicKey, PublicKeyInitData } from "@solana/web3.js"; +import { PublicKey, PublicKeyInitData, TransactionInstruction } from "@solana/web3.js"; import { BN } from "@coral-xyz/anchor"; const CHAIN_ID_BYTE_SIZE = 2; @@ -39,13 +39,13 @@ export const U64 = { MAX: new BN((2n**64n - 1n).toString()), to: (amount: number, unit: number) => { const ret = new BN(Math.round(amount * unit)); - + if (ret.isNeg()) throw new Error("Value negative"); - + if (ret.bitLength() > 64) - throw new Error("Value too large"); - + throw new Error("Value too large"); + return ret; }, from: (amount: BN, unit: number) => amount.toNumber() / unit, @@ -57,8 +57,41 @@ export function derivePda( programId: PublicKeyInitData ) { const toBytes = (s: string | Uint8Array) => typeof s === "string" ? encoding.bytes.encode(s) : s; - return PublicKey.findProgramAddressSync( + return PublicKey.findProgramAddressSync( Array.isArray(seeds) ? seeds.map(toBytes) : [toBytes(seeds as Seed)], new PublicKey(programId), )[0]; -} \ No newline at end of file +} + +// governance utils + +export function serializeInstruction(ix: TransactionInstruction): Buffer { + const programId = ix.programId.toBuffer(); + const accountsLen = Buffer.alloc(2); + accountsLen.writeUInt16BE(ix.keys.length); + const accounts = Buffer.concat(ix.keys.map((account) => { + const isSigner = Buffer.alloc(1); + isSigner.writeUInt8(account.isSigner ? 1 : 0); + const isWritable = Buffer.alloc(1); + isWritable.writeUInt8(account.isWritable ? 1 : 0); + const pubkey = account.pubkey.toBuffer(); + return Buffer.concat([pubkey, isSigner, isWritable]); + })) + const dataLen = Buffer.alloc(2); + dataLen.writeUInt16BE(ix.data.length); + return Buffer.concat([programId, accountsLen, accounts, dataLen, ix.data]); +} + +export function appendGovernanceHeader(data: Buffer, governanceProgramId: PublicKey): Buffer { + const module = Buffer.from("GeneralPurposeGovernance".padStart(32, "\0")); + const action = Buffer.alloc(1); + action.writeUInt8(2); // SolanaCall + const chainId = Buffer.alloc(2); + chainId.writeUInt16BE(1); // solana + const programId = governanceProgramId.toBuffer(); + return Buffer.concat([module, action, chainId, programId, data]); +} + +// sentinel values used in governance +export const OWNER = new PublicKey(Buffer.from("owner".padEnd(32, "\0"))); +export const PAYER = new PublicKey(Buffer.from("payer".padEnd(32, "\0"))); From 7b974ccf0e7e3c848db4f44257389d1a8cb4fa83 Mon Sep 17 00:00:00 2001 From: Csongor Kiss Date: Wed, 24 Apr 2024 00:34:11 +0100 Subject: [PATCH 2/3] Tiltfile: target solana builder explicitly instead of last stage --- Tiltfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Tiltfile b/Tiltfile index 28df548f4..068f75c35 100644 --- a/Tiltfile +++ b/Tiltfile @@ -28,6 +28,7 @@ docker_build( context = "./", only = ["./sdk", "./solana"], ignore=["./sdk/__tests__", "./sdk/Dockerfile", "./sdk/ci.yaml", "./sdk/**/dist", "./sdk/node_modules", "./sdk/**/node_modules"], + target = "builder", dockerfile = "./solana/Dockerfile", ) k8s_yaml_with_ns("./solana/solana-devnet.yaml") From 538222e86a6adeb66e4ef46c0a994359479c1912 Mon Sep 17 00:00:00 2001 From: Nikhil Suri Date: Mon, 3 Jun 2024 15:18:44 -0700 Subject: [PATCH 3/3] solana: ts: add functions to get inbound/outbound limit instructions --- solana/ts/sdk/ntt.ts | 54 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/solana/ts/sdk/ntt.ts b/solana/ts/sdk/ntt.ts index 300e8f50f..de7ed3f2a 100644 --- a/solana/ts/sdk/ntt.ts +++ b/solana/ts/sdk/ntt.ts @@ -757,15 +757,30 @@ export class NTT { chain: ChainName limit: BN }) { - const ix = await this.program.methods.setOutboundLimit({ + const ix = await this.createOutboundLimitInstruction({ + owner: args.owner.publicKey, limit: args.limit - }) + }); + return this.sendAndConfirmTransaction( + new Transaction().add(ix), + [args.owner] + ); + } + + async createOutboundLimitInstruction(args: { + owner: PublicKey + limit: BN + }) { + return this.program.methods + .setOutboundLimit({ + limit: args.limit + }) .accounts({ - owner: args.owner.publicKey, + owner: args.owner, config: this.configAccountAddress(), rateLimit: this.outboxRateLimitAccountAddress(), - }).instruction(); - return sendAndConfirmTransaction(this.program.provider.connection, new Transaction().add(ix), [args.owner]); + }) + .instruction(); } async setInboundLimit(args: { @@ -773,16 +788,33 @@ export class NTT { chain: ChainName limit: BN }) { - const ix = await this.program.methods.setInboundLimit({ - chainId: { id: toChainId(args.chain) }, + const ix = await this.createInboundLimitInstruction({ + owner: args.owner.publicKey, + chain: args.chain, limit: args.limit - }) + }); + return this.sendAndConfirmTransaction( + new Transaction().add(ix), + [args.owner] + ); + } + + async createInboundLimitInstruction(args: { + owner: PublicKey + chain: ChainName + limit: BN + }) { + return this.program.methods + .setInboundLimit({ + chainId: { id: toChainId(args.chain) }, + limit: args.limit + }) .accounts({ - owner: args.owner.publicKey, + owner: args.owner, config: this.configAccountAddress(), rateLimit: this.inboxRateLimitAccountAddress(args.chain), - }).instruction(); - return sendAndConfirmTransaction(this.program.provider.connection, new Transaction().add(ix), [args.owner]); + }) + .instruction(); } async createReceiveWormholeMessageInstruction(args: {