Skip to content

Commit

Permalink
Simple versioned transaction support (coral-xyz#2427)
Browse files Browse the repository at this point in the history
  • Loading branch information
Henry-E authored Mar 7, 2023
1 parent 1dc16d6 commit 30083bd
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- client: Add support for multithreading to the rust client: use flag `--multithreaded` ([#2321](https://github.com/coral-xyz/anchor/pull/2321)).
- client: Add `async_rpc` a method which returns a nonblocking solana rpc client ([2322](https://github.com/coral-xyz/anchor/pull/2322)).
- avm, cli: Use the `rustls-tls` feature of `reqwest` so that users don't need OpenSSL installed ([#2385](https://github.com/coral-xyz/anchor/pull/2385)).
- ts: Add `VersionedTransaction` support. Methods in the `Provider` class and `Wallet` interface now use the argument `tx: Transaction | VersionedTransaction` ([2427](https://github.com/coral-xyz/anchor/pull/2427)).
- cli: Add `--arch sbf` option to compile programs using `cargo build-sbf` ([#2398](https://github.com/coral-xyz/anchor/pull/2398)).

### Fixes
Expand Down
147 changes: 147 additions & 0 deletions tests/misc/tests/misc/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
SystemProgram,
Message,
VersionedTransaction,
AddressLookupTableProgram,
TransactionMessage,
} from "@solana/web3.js";
import {
TOKEN_PROGRAM_ID,
Expand Down Expand Up @@ -61,6 +63,151 @@ const miscTest = (
assert.strictEqual(dataAccount.data, 99);
});

it("Can send VersionedTransaction", async () => {
// Create the lookup table
const recentSlot = await provider.connection.getSlot();
const [loookupTableInstruction, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: provider.publicKey,
payer: provider.publicKey,
recentSlot,
});
const extendInstruction = AddressLookupTableProgram.extendLookupTable({
payer: provider.publicKey,
authority: provider.publicKey,
lookupTable: lookupTableAddress,
addresses: [provider.publicKey, SystemProgram.programId],
});
let createLookupTableTx = new VersionedTransaction(
new TransactionMessage({
instructions: [loookupTableInstruction, extendInstruction],
payerKey: program.provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message()
);
type SendParams = Parameters<typeof provider.sendAndConfirm>;
const testThis: SendParams = [
new VersionedTransaction(
new TransactionMessage({
instructions: [loookupTableInstruction, extendInstruction],
payerKey: program.provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message()
),
];
await provider.sendAndConfirm(createLookupTableTx, [], {
skipPreflight: true,
});

// Use the lookup table in a transaction
const transferAmount = 1_000_000;
const lookupTableAccount = await provider.connection
.getAddressLookupTable(lookupTableAddress)
.then((res) => res.value);
const target = Keypair.generate();
let transferInstruction = SystemProgram.transfer({
fromPubkey: provider.publicKey,
lamports: transferAmount,
toPubkey: target.publicKey,
});
let transferUsingLookupTx = new VersionedTransaction(
new TransactionMessage({
instructions: [transferInstruction],
payerKey: program.provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message([lookupTableAccount])
);
await provider.simulate(transferUsingLookupTx, [], "processed");
await provider.sendAndConfirm(transferUsingLookupTx, [], {
skipPreflight: true,
commitment: "confirmed",
});
let newBalance = await provider.connection.getBalance(
target.publicKey,
"confirmed"
);
assert.strictEqual(newBalance, transferAmount);

// Test sendAll with versioned transaction
let oneTransferUsingLookupTx = new VersionedTransaction(
new TransactionMessage({
instructions: [
SystemProgram.transfer({
fromPubkey: provider.publicKey,
// Needed to make the transactions distinct
lamports: transferAmount + 1,
toPubkey: target.publicKey,
}),
],
payerKey: program.provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message([lookupTableAccount])
);
let twoTransferUsingLookupTx = new VersionedTransaction(
new TransactionMessage({
instructions: [
SystemProgram.transfer({
fromPubkey: provider.publicKey,
lamports: transferAmount,
toPubkey: target.publicKey,
}),
],
payerKey: program.provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message([lookupTableAccount])
);
await provider.sendAll(
[{ tx: oneTransferUsingLookupTx }, { tx: twoTransferUsingLookupTx }],
{ skipPreflight: true, commitment: "confirmed" }
);
newBalance = await provider.connection.getBalance(
target.publicKey,
"confirmed"
);
assert.strictEqual(newBalance, transferAmount * 3 + 1);
});

it("Can send VersionedTransaction with extra signatures", async () => {
// Test sending with signatures
const initSpace = 100;
const rentExemptAmount =
await provider.connection.getMinimumBalanceForRentExemption(initSpace);

const newAccount = Keypair.generate();
let createAccountIx = SystemProgram.createAccount({
fromPubkey: provider.publicKey,
lamports: rentExemptAmount,
newAccountPubkey: newAccount.publicKey,
programId: program.programId,
space: initSpace,
});
let createAccountTx = new VersionedTransaction(
new TransactionMessage({
instructions: [createAccountIx],
payerKey: provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message()
);
await provider.simulate(createAccountTx, [], "processed");
await provider.sendAndConfirm(createAccountTx, [newAccount], {
skipPreflight: false,
commitment: "confirmed",
});
let newAccountInfo = await provider.connection.getAccountInfo(
newAccount.publicKey
);
assert.strictEqual(
newAccountInfo.owner.toBase58(),
program.programId.toBase58()
);
});

it("Can embed programs into genesis from the Anchor.toml", async () => {
const pid = new anchor.web3.PublicKey(
"FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr"
Expand Down
29 changes: 24 additions & 5 deletions ts/packages/anchor/src/nodewallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Buffer } from "buffer";
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
import {
Keypair,
PublicKey,
Transaction,
VersionedTransaction,
} from "@solana/web3.js";
import { Wallet } from "./provider";
import { isVersionedTransaction } from "./utils/common.js";

/**
* Node only wallet.
Expand Down Expand Up @@ -30,14 +36,27 @@ export default class NodeWallet implements Wallet {
return new NodeWallet(payer);
}

async signTransaction(tx: Transaction): Promise<Transaction> {
tx.partialSign(this.payer);
async signTransaction<T extends Transaction | VersionedTransaction>(
tx: T
): Promise<T> {
if (isVersionedTransaction(tx)) {
tx.sign([this.payer]);
} else {
tx.partialSign(this.payer);
}

return tx;
}

async signAllTransactions(txs: Transaction[]): Promise<Transaction[]> {
async signAllTransactions<T extends Transaction | VersionedTransaction>(
txs: T[]
): Promise<T[]> {
return txs.map((t) => {
t.partialSign(this.payer);
if (isVersionedTransaction(t)) {
t.sign([this.payer]);
} else {
t.partialSign(this.payer);
}
return t;
});
}
Expand Down
Loading

0 comments on commit 30083bd

Please sign in to comment.