From 8bec3dcfd613cf31e83c0f9c6797318a4b033f36 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 01:25:31 +0000 Subject: [PATCH 1/4] Add a transaction v1 example and document v1 support across the toolchain Solana's v1 transaction format (SIMD-0385) raises the transaction size limit from 1,232 to 4,096 bytes and moves the compute budget into the message. Programs need no change for it; tests and clients do. basics/transaction-v1/, in Anchor v2, Anchor v1, Pinocchio and native Rust: a program that stores a 3,000 byte document in one instruction, which only fits in a v1 transaction. Its LiteSVM 0.16 tests build the v1 transactions by hand, measure them against both size limits, and show the config fields that replace ComputeBudget instructions, including that an unset field means zero rather than the default. The four directories are standalone workspaces: the root workspace pins LiteSVM 0.13.1 through anchor-v2-testing and solana-kite, and 0.13.1 and 0.16.0 cannot share a lockfile. docs/transaction-v1.md records what v1 changes, where it is live, and which of the tools the examples build on can send it, checked against the pinned versions. README, CHANGELOG and llms.txt link to it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014EotgxjmAGtATDFVnBtjXj --- CHANGELOG.md | 28 +++ Cargo.toml | 6 + README.md | 12 +- basics/transaction-v1/anchor-v1/Anchor.toml | 21 ++ basics/transaction-v1/anchor-v1/CHANGELOG.md | 6 + basics/transaction-v1/anchor-v1/Cargo.toml | 14 ++ basics/transaction-v1/anchor-v1/README.md | 56 +++++ .../programs/transaction-v1/Cargo.toml | 52 ++++ .../programs/transaction-v1/Xargo.toml | 2 + .../transaction-v1/src/instructions/mod.rs | 3 + .../transaction-v1/src/instructions/store.rs | 32 +++ .../programs/transaction-v1/src/lib.rs | 35 +++ .../transaction-v1/src/state/document.rs | 17 ++ .../programs/transaction-v1/src/state/mod.rs | 3 + .../tests/test_transaction_v1.rs | 234 +++++++++++++++++ basics/transaction-v1/anchor/Anchor.toml | 21 ++ basics/transaction-v1/anchor/CHANGELOG.md | 6 + basics/transaction-v1/anchor/Cargo.toml | 23 ++ basics/transaction-v1/anchor/README.md | 56 +++++ .../anchor/programs/transaction-v1/Cargo.toml | 58 +++++ .../anchor/programs/transaction-v1/Xargo.toml | 2 + .../transaction-v1/src/instructions/mod.rs | 3 + .../transaction-v1/src/instructions/store.rs | 37 +++ .../anchor/programs/transaction-v1/src/lib.rs | 35 +++ .../transaction-v1/src/state/document.rs | 19 ++ .../programs/transaction-v1/src/state/mod.rs | 3 + .../tests/test_transaction_v1.rs | 238 ++++++++++++++++++ basics/transaction-v1/native/CHANGELOG.md | 6 + basics/transaction-v1/native/Cargo.toml | 13 + basics/transaction-v1/native/README.md | 52 ++++ .../transaction-v1/native/program/Cargo.toml | 40 +++ .../transaction-v1/native/program/src/lib.rs | 88 +++++++ .../program/tests/test_transaction_v1.rs | 221 ++++++++++++++++ basics/transaction-v1/pinocchio/CHANGELOG.md | 6 + basics/transaction-v1/pinocchio/Cargo.toml | 13 + basics/transaction-v1/pinocchio/README.md | 52 ++++ .../pinocchio/program/Cargo.toml | 41 +++ .../pinocchio/program/src/lib.rs | 94 +++++++ .../program/tests/test_transaction_v1.rs | 221 ++++++++++++++++ docs/transaction-v1.md | 145 +++++++++++ llms.txt | 1 + 41 files changed, 2014 insertions(+), 1 deletion(-) create mode 100644 basics/transaction-v1/anchor-v1/Anchor.toml create mode 100644 basics/transaction-v1/anchor-v1/CHANGELOG.md create mode 100644 basics/transaction-v1/anchor-v1/Cargo.toml create mode 100644 basics/transaction-v1/anchor-v1/README.md create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/Xargo.toml create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/mod.rs create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/store.rs create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/src/lib.rs create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/document.rs create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/mod.rs create mode 100644 basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs create mode 100644 basics/transaction-v1/anchor/Anchor.toml create mode 100644 basics/transaction-v1/anchor/CHANGELOG.md create mode 100644 basics/transaction-v1/anchor/Cargo.toml create mode 100644 basics/transaction-v1/anchor/README.md create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs create mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs create mode 100644 basics/transaction-v1/native/CHANGELOG.md create mode 100644 basics/transaction-v1/native/Cargo.toml create mode 100644 basics/transaction-v1/native/README.md create mode 100644 basics/transaction-v1/native/program/Cargo.toml create mode 100644 basics/transaction-v1/native/program/src/lib.rs create mode 100644 basics/transaction-v1/native/program/tests/test_transaction_v1.rs create mode 100644 basics/transaction-v1/pinocchio/CHANGELOG.md create mode 100644 basics/transaction-v1/pinocchio/Cargo.toml create mode 100644 basics/transaction-v1/pinocchio/README.md create mode 100644 basics/transaction-v1/pinocchio/program/Cargo.toml create mode 100644 basics/transaction-v1/pinocchio/program/src/lib.rs create mode 100644 basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs create mode 100644 docs/transaction-v1.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 773b599c6..73b366e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to this repository are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2026-09-03] - Transaction v1 example + +Solana's v1 transaction format (SIMD-0385) raises the transaction size limit from +1,232 to 4,096 bytes and moves the compute budget out of ComputeBudget instructions +and into the message. It is live on testnet, pending on devnet, and targeted for +mainnet on 9 September 2026. Programs need no change for it; tests and clients do. + +### Added + +- `basics/transaction-v1/`, in Anchor v2, Anchor v1, Pinocchio and native Rust: a + program that stores a 3,000 byte document in one instruction, which only fits in a + v1 transaction. Its LiteSVM tests build the v1 transactions by hand, measure them + against both size limits, and show the config fields that replace ComputeBudget + instructions, including that an unset field means zero rather than the default. + The four directories are standalone Cargo workspaces on LiteSVM 0.16.0, the first + release that executes v1 transactions. +- `docs/transaction-v1.md`: what v1 changes, where it is live, and which of the + tools the examples build on can send it, checked against the pinned versions. + +### Note + +- Every other example's tests still send legacy transactions, and keep passing. + Moving them is blocked on `solana-kite` 0.4.0 and `anchor-v2-testing`, which pin + LiteSVM 0.13.1, and on `quasar-svm`, which is on a `solana-message` without the + `v1` module. The root workspace cannot take LiteSVM 0.16 until the first two move: + 0.13.1 and 0.16.0 pin different `solana-instruction` 3.x patch releases and so + cannot share a lockfile. The root `Cargo.toml` says why the example is not a member. + ## [2026-08-21] - Anchor v1 kept alongside Anchor v2 Anchor v1 is expected to stay on long-term support, and many deployed programs diff --git a/Cargo.toml b/Cargo.toml index f3ca804b2..05f6f8acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,12 @@ # declares its own `[workspace]`, so `anchor build` inside one resolves there. The # cost is that `cargo fmt` and `cargo clippy` never see them; the Anchor v1 workflow # compiles and tests them instead. +# +# `basics/transaction-v1/` is absent for a different reason. Its tests need LiteSVM +# 0.16, the first release that executes v1 transactions, while `anchor-v2-testing` +# and `solana-kite` pin 0.13.1, and the two cannot share this lockfile (each pins a +# different `solana-instruction` 3.x patch release). All four of its framework +# directories are standalone workspaces; the framework workflows build and test them. members = [ #basics "basics/account-data/native/program", diff --git a/README.md b/README.md index abbe80466..a73cb8a16 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ _Solana program examples ('smart contracts') in Anchor 2, Anchor 1, Quasar, Pinocchio, native Rust, and sBPF assembly. Focused on financial software, plus the basics, tokens, Token Extensions, state compression, and more._ -Working, tested, up-to-date examples of common Solana programs (what other chains call smart contracts), maintained by [Quicknode](https://www.quicknode.com/chains/solana). Current as of August 2026 (see [CHANGELOG.md](./CHANGELOG.md)): every example builds and passes CI on **Anchor 2.0.0-rc.1**, the current multi-file program layout (one file per instruction handler, account type, etc), and [LiteSVM](https://github.com/LiteSVM/litesvm) tests rather than the older `solana-test-validator` / web3.js stack. Every Anchor example also keeps its last **Anchor 1.1.2** version in a sibling `anchor-v1/` directory, built and tested by its own CI job, for programs staying on the Anchor v1 LTS line. +Working, tested, up-to-date examples of common Solana programs (what other chains call smart contracts), maintained by [Quicknode](https://www.quicknode.com/chains/solana). Current as of September 2026 (see [CHANGELOG.md](./CHANGELOG.md)): every example builds and passes CI on **Anchor 2.0.0-rc.1**, the current multi-file program layout (one file per instruction handler, account type, etc), and [LiteSVM](https://github.com/LiteSVM/litesvm) tests rather than the older `solana-test-validator` / web3.js stack. Every Anchor example also keeps its last **Anchor 1.1.2** version in a sibling `anchor-v1/` directory, built and tested by its own CI job, for programs staying on the Anchor v1 LTS line. [![Anchor v2](../../actions/workflows/anchor.yml/badge.svg)](../../actions/workflows/anchor.yml) [![Anchor v1](../../actions/workflows/anchor-v1.yml/badge.svg)](../../actions/workflows/anchor-v1.yml) [![Quasar](../../actions/workflows/quasar.yml/badge.svg)](../../actions/workflows/quasar.yml) [![Pinocchio](../../actions/workflows/pinocchio.yml/badge.svg)](../../actions/workflows/pinocchio.yml) [![Native](../../actions/workflows/native.yml/badge.svg)](../../actions/workflows/native.yml) [![ASM](../../actions/workflows/solana-asm.yml/badge.svg)](../../actions/workflows/solana-asm.yml) @@ -183,6 +183,12 @@ Send SOL between two accounts. [⚓ Anchor v2](./basics/transfer-sol/anchor) [⚓ Anchor v1](./basics/transfer-sol/anchor-v1) [💫 Quasar](./basics/transfer-sol/quasar) [🤥 Pinocchio](./basics/transfer-sol/pinocchio) [🦀 Native](./basics/transfer-sol/native) [🧬 ASM](./basics/transfer-sol/asm) +### Transaction v1 + +Store a 3,000 byte document in one instruction using Solana's v1 transaction format: 4,096 bytes instead of 1,232, and a transaction config in the message in place of ComputeBudget instructions. The program needs no change for v1; the LiteSVM tests are what build the v1 transactions. See [docs/transaction-v1.md](./docs/transaction-v1.md) for what v1 changes, where it is live, and which tools support it. + +[⚓ Anchor v2](./basics/transaction-v1/anchor) [⚓ Anchor v1](./basics/transaction-v1/anchor-v1) [🤥 Pinocchio](./basics/transaction-v1/pinocchio) [🦀 Native](./basics/transaction-v1/native) + ### Pyth Price Feeds An **oracle** brings real-world market prices - a dollar, a stock, a token - [onchain](https://solana.com/docs/terminology#onchain), like a Bloomberg terminal feeding live quotes. [Pyth](https://pyth.network/) publishes low-latency prices from institutional sources, each in its own price feed account. This example reads a feed and logs its price, confidence interval, and exponent - the building block an AMM, lending market, or vault uses to value assets. @@ -399,6 +405,10 @@ Yes. "Program" is Solana's term for what other chains call a smart contract: cod Start with the [escrow example](./finance/escrow/anchor/), the best first finance program to learn: one state PDA, one vault, and three instruction handlers that swap two tokens atomically. There is also a [30-minute video walkthrough](https://www.youtube.com/watch?v=B5eBWWQfQuM) building it from scratch. +### Do these examples work with Solana transaction v1? + +Yes. The v1 format (4,096 byte transactions, live on testnet and targeted for mainnet on 9 September 2026) changes how a client builds a transaction, not what a program sees, so every program here can be called through v1 as is. The [transaction v1 example](./basics/transaction-v1/anchor/) sends v1 transactions from its LiteSVM tests and shows the config fields that replace ComputeBudget instructions. The other examples' tests still send legacy transactions, because the test helpers they share (`solana-kite` and Anchor's `anchor-v2-testing`) are pinned to a LiteSVM release that predates v1. [docs/transaction-v1.md](./docs/transaction-v1.md) tracks the status of each tool. + ### How do I test a Solana program without running a validator? Use [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm): Rust integration tests that run the program in-process, orders of magnitude faster than `solana-test-validator`. Every example in this repository tests this way, and `anchor init` scaffolds it by default since Anchor 1.0. diff --git a/basics/transaction-v1/anchor-v1/Anchor.toml b/basics/transaction-v1/anchor-v1/Anchor.toml new file mode 100644 index 000000000..4bf813a9b --- /dev/null +++ b/basics/transaction-v1/anchor-v1/Anchor.toml @@ -0,0 +1,21 @@ +[toolchain] +# Match the repo package manager (pnpm-lock.yaml at root); avoids Anchor's yarn default. +package_manager = "pnpm" +# Agave 4.2 is the first release with the v1 transaction format. Its +# `solana-test-validator` activates the feature at genesis, so a v1 +# transaction that passes these LiteSVM tests can be sent to it as is. +solana_version = "4.2.2" + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +transaction_v1_anchor_program = "G2RNhZWhh8f3jjY9Z6CQyztFR6PGC5iM8yrbNEjvSuKa" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "cargo test" diff --git a/basics/transaction-v1/anchor-v1/CHANGELOG.md b/basics/transaction-v1/anchor-v1/CHANGELOG.md new file mode 100644 index 000000000..95c8d15fd --- /dev/null +++ b/basics/transaction-v1/anchor-v1/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 2026-09-03 + +Added: a program that stores a 3,000 byte document in one instruction, tested with +LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/anchor-v1/Cargo.toml b/basics/transaction-v1/anchor-v1/Cargo.toml new file mode 100644 index 000000000..f39770481 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +members = [ + "programs/*" +] +resolver = "2" + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/basics/transaction-v1/anchor-v1/README.md b/basics/transaction-v1/anchor-v1/README.md new file mode 100644 index 000000000..d68376de4 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/README.md @@ -0,0 +1,56 @@ +# Solana Transaction v1 (Anchor) + +> [!NOTE] +> This is the **Anchor v1** copy of this example, kept for programs staying on the +> Anchor v1 LTS line. Every `anchor` command on this page needs the v1 CLI: +> `avm install 1.1.2 && avm use 1.1.2`. The Anchor v2 version of this example is in +> [`../anchor`](../anchor/). + +A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). Nothing in the program is specific to v1: an Anchor program reads its accounts and instruction data the same way whichever format carried them, and no Anchor change is needed to be called through v1. The tests are where v1 shows up, because the client builds the transaction. + +See also the [Anchor v2](../anchor/), [Pinocchio](../pinocchio/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. + +## Major concepts + +**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. + +**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as the `document: Vec` argument exactly as a 300 byte one would. + +**The document account.** `Document`, a [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer. There is no `InitSpace`: `Document::required_space` sizes the account to each document (discriminator, borsh length prefix, bytes), and the payer pays that [rent](https://solana.com/docs/references/terminology#rent). + +## Lifecycle + +A payer signs a transaction carrying one `store_document` instruction with the document as its argument. The `StoreDocumentAccountConstraints` `init` constraint creates the PDA sized to the document and the handler rejects an empty document (`DocumentError::EmptyDocument`) before copying it in. + +## Setup + +From this directory (`basics/transaction-v1/anchor-v1/`): + +```bash +anchor build +``` + +Prerequisites: [Agave](https://docs.anza.xyz/) CLI 4.2 or later (`Anchor.toml` `[toolchain]` pins 4.2.2, the first line with the v1 format), [Anchor](https://www.anchor-lang.com/docs) v1. + +Like every `anchor-v1/` directory, this example is its own Cargo workspace outside the repository root workspace. + +## Testing + +Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm). No local validator. + +```bash +anchor test +``` + +This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example builds its transactions by hand rather than with `solana-kite`, which the other Anchor v1 examples use: kite 0.4.0 pins LiteSVM 0.13.1, which cannot send a v1 transaction. The suite covers: + +- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. +- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. +- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. +- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). + +LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. + +## Sending v1 transactions to a real cluster + +The v1 format is behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). It needs Agave 4.2 or later and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. Anchor's TypeScript client (`@coral-xyz/anchor`) builds its transactions with `@solana/web3.js` 1.x, which can read v1 transactions but not build them, so a v1 transaction to an Anchor program is built with `@solana/kit` 8 or later, or in Rust as in the tests here. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the activation status per cluster and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml new file mode 100644 index 000000000..c704d5bca --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "transaction-v1-anchor-program" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "transaction_v1_anchor_program" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +anchor-debug = [] +custom-heap = [] +custom-panic = [] + +[dependencies] +anchor-lang = "1.1.2" + +[dev-dependencies] +# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the +# compute unit limit, priority fee, loaded-accounts data size limit and heap +# size from the message's transaction config rather than from ComputeBudget +# instructions. +# +# The other Anchor v1 examples build their transactions with `solana-kite`, +# which pins LiteSVM 0.13.1 and so cannot send a v1 transaction yet. This +# example builds them by hand. Move back to kite once it is on LiteSVM 0.16 +# or later. +litesvm = "0.16.0" +borsh = "1.6.1" +solana-keypair = "3.1" +# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 +# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. +solana-message = "4.2" +solana-native-token = "3.0" +# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. +solana-packet = "4.2" +solana-transaction = "4.1" +solana-transaction-error = "3.3" +# The SDK's serializer. `wincode::serialize` on a `VersionedTransaction` gives +# the exact bytes that would go over the wire, which is how the tests measure +# transaction size. +wincode = "0.5" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Xargo.toml b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/mod.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/mod.rs new file mode 100644 index 000000000..89cbfd5e6 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/mod.rs @@ -0,0 +1,3 @@ +pub mod store; + +pub use store::*; diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/store.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/store.rs new file mode 100644 index 000000000..76fcf89e4 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/instructions/store.rs @@ -0,0 +1,32 @@ +use crate::{state::Document, DocumentError}; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +#[instruction(document: Vec)] +pub struct StoreDocumentAccountConstraints<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + /// Sized to this document exactly, so the rent matches what was stored. + #[account( + init, + payer = payer, + space = Document::required_space(document.len()), + seeds = [ + Document::SEED_PREFIX, + payer.key().as_ref(), + ], + bump, + )] + pub document_account: Account<'info, Document>, + pub system_program: Program<'info, System>, +} + +pub fn handle_store_document( + context: Context, + document: Vec, +) -> Result<()> { + require!(!document.is_empty(), DocumentError::EmptyDocument); + context.accounts.document_account.data = document; + Ok(()) +} diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/lib.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/lib.rs new file mode 100644 index 000000000..d5aaa6513 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/lib.rs @@ -0,0 +1,35 @@ +//! Store a document of arbitrary size in a program-owned account, in one +//! instruction. +//! +//! The program itself has nothing to do with transaction v1: it reads the +//! accounts and instruction data it is given like any other program, and does +//! not know or care which transaction format carried them. What v1 changes is +//! how much a single instruction can carry: a 3,000 byte document is beyond +//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it +//! in a v1 transaction (up to 4,096 bytes) instead. + +use anchor_lang::prelude::*; +use instructions::*; + +pub mod instructions; +pub mod state; + +declare_id!("G2RNhZWhh8f3jjY9Z6CQyztFR6PGC5iM8yrbNEjvSuKa"); + +#[program] +pub mod transaction_v1_anchor_program { + use super::*; + + pub fn store_document( + context: Context, + document: Vec, + ) -> Result<()> { + store::handle_store_document(context, document) + } +} + +#[error_code] +pub enum DocumentError { + #[msg("A document cannot be empty")] + EmptyDocument, +} diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/document.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/document.rs new file mode 100644 index 000000000..f915be3c1 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/document.rs @@ -0,0 +1,17 @@ +use anchor_lang::prelude::*; + +// There is deliberately no `InitSpace` here: the account is sized to each +// document rather than to a compile-time maximum. +#[account] +pub struct Document { + pub data: Vec, +} + +impl Document { + pub const SEED_PREFIX: &'static [u8; 8] = b"document"; + + /// Discriminator, then the borsh length prefix, then the bytes. + pub fn required_space(document_len: usize) -> usize { + Self::DISCRIMINATOR.len() + 4 + document_len + } +} diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/mod.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/mod.rs new file mode 100644 index 000000000..d0889e2ad --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod document; + +pub use document::*; diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs new file mode 100644 index 000000000..b83323c06 --- /dev/null +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs @@ -0,0 +1,234 @@ +//! The program is exercised with both transaction formats: legacy for a small +//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also +//! cover the part of the format that is easy to get wrong: the transaction +//! config that replaces ComputeBudget instructions, whose unset fields mean +//! zero rather than "use the default". + +use { + anchor_lang::{ + solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, + InstructionData, ToAccountMetas, + }, + borsh::BorshDeserialize, + litesvm::LiteSVM, + solana_keypair::{Keypair, Signer}, + solana_message::{v1, VersionedMessage}, + solana_native_token::LAMPORTS_PER_SOL, + solana_packet::PACKET_DATA_SIZE, + solana_transaction::{versioned::VersionedTransaction, Transaction}, + solana_transaction_error::TransactionError, + transaction_v1_anchor_program::state::Document, +}; + +/// What LiteSVM charges per signature, and what mainnet has charged since +/// genesis. +const BASE_FEE_LAMPORTS: u64 = 5_000; + +/// Enough for the account creation and the copy. Unlike a legacy transaction, +/// a v1 transaction has no 200,000 CU default to fall back on: leaving the +/// limit unset means zero. +const COMPUTE_UNIT_LIMIT: u32 = 50_000; + +/// The payer, the new account, the system program and the program itself +/// together load well under 64 KiB. Like the compute unit limit, this defaults +/// to zero when unset. +const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; + +/// A v1 transaction's config carries the resource limits that legacy and v0 +/// transactions express with ComputeBudget program instructions. Every test +/// that expects to succeed starts from this one. +fn transaction_config() -> v1::TransactionConfig { + v1::TransactionConfig::empty() + .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) +} + +/// The account as Anchor lays it out: discriminator, then the borsh `Vec`. +#[derive(BorshDeserialize)] +struct DocumentAccount { + _discriminator: [u8; 8], + data: Vec, +} + +fn setup() -> (LiteSVM, Pubkey, Keypair) { + let program_id = transaction_v1_anchor_program::id(); + let mut svm = LiteSVM::new(); + let program_bytes = include_bytes!("../../../target/deploy/transaction_v1_anchor_program.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); + (svm, program_id, payer) +} + +/// The instruction and the PDA it will create. +fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Instruction, Pubkey) { + let (document_address, _bump) = + Pubkey::find_program_address(&[Document::SEED_PREFIX, payer.as_ref()], program_id); + let instruction = Instruction::new_with_bytes( + *program_id, + &transaction_v1_anchor_program::instruction::StoreDocument { + document: document.to_vec(), + } + .data(), + transaction_v1_anchor_program::accounts::StoreDocumentAccountConstraints { + payer: *payer, + document_account: document_address, + system_program: system_program::id(), + } + .to_account_metas(None), + ); + (instruction, document_address) +} + +/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 +/// counterpart of `Message::new_with_blockhash`; the config rides in the +/// message header, so there is no ComputeBudget instruction to append. +fn v1_transaction( + svm: &LiteSVM, + payer: &Keypair, + instructions: &[Instruction], + config: v1::TransactionConfig, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + &payer.pubkey(), + instructions, + svm.latest_blockhash(), + config, + ) + .unwrap(); + VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() +} + +/// The bytes that would be sent to an RPC node. +fn wire_size(transaction: &VersionedTransaction) -> usize { + wincode::serialize(transaction).unwrap().len() +} + +/// A document with a recognizable pattern, so a partial or shifted copy +/// would not pass the equality check. +fn sample_document(length: usize) -> Vec { + (0..length).map(|i| (i % 251) as u8).collect() +} + +fn stored_document(svm: &LiteSVM, document_address: &Pubkey) -> Vec { + let account = svm.get_account(document_address).unwrap(); + DocumentAccount::try_from_slice(&account.data).unwrap().data +} + +#[test] +fn stores_a_document_too_large_for_a_legacy_transaction() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); + + // Over the legacy limit, under the v1 one. LiteSVM does not enforce either + // (a validator's packet layer does), so the test checks the size itself. + let size = wire_size(&transaction); + assert!( + size > PACKET_DATA_SIZE, + "{size} bytes fits a legacy transaction" + ); + assert!( + size <= v1::MAX_TRANSACTION_SIZE, + "{size} bytes is over the v1 limit" + ); + + svm.send_transaction(transaction).unwrap(); + + let account = svm.get_account(&document_address).unwrap(); + assert_eq!(account.owner, program_id); + assert_eq!(account.data.len(), Document::required_space(document.len())); + assert_eq!(stored_document(&svm, &document_address), document); +} + +#[test] +fn a_legacy_transaction_still_works_for_a_small_document() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + // Nothing about the program changed: the same instruction goes through the + // legacy format when it fits. + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&payer.pubkey()), + &[&payer], + svm.latest_blockhash(), + ); + assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); + + svm.send_transaction(transaction).unwrap(); + + assert_eq!(stored_document(&svm, &document_address), document); +} + +#[test] +fn the_transaction_config_replaces_compute_budget_instructions() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + let rent = svm.minimum_balance_for_rent_exemption(Document::required_space(document.len())); + + // A v1 priority fee is a total in lamports, not a price per compute unit. + let priority_fee_lamports = 10_000; + let config = transaction_config().with_priority_fee(priority_fee_lamports); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + + // v1 moved the signatures to the end, so the first byte on the wire is the + // message version rather than a signature count. 0x81 means v1. + assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); + + let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); + let metadata = svm.send_transaction(transaction).unwrap(); + let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); + + assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); + assert_eq!( + balance_before - balance_after, + rent + BASE_FEE_LAMPORTS + priority_fee_lamports + ); +} + +#[test] +fn unset_config_fields_mean_zero_not_the_default() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + + // No loaded-accounts data size limit: the transaction fails before any + // program runs, because zero bytes of account data may be loaded. + let transaction = v1_transaction( + &svm, + &payer, + std::slice::from_ref(&instruction), + v1::TransactionConfig::empty(), + ); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert_eq!( + failure.err, + TransactionError::MaxLoadedAccountsDataSizeExceeded + ); + + // Data size limit set, compute unit limit not: the program starts with a + // budget of zero and fails on its first instruction. + let config = v1::TransactionConfig::empty() + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert!(matches!( + failure.err, + TransactionError::InstructionError(0, _) + )); + assert!( + failure + .meta + .logs + .iter() + .any(|log| log.contains("exceeded CUs")), + "{:?}", + failure.meta.logs + ); +} diff --git a/basics/transaction-v1/anchor/Anchor.toml b/basics/transaction-v1/anchor/Anchor.toml new file mode 100644 index 000000000..3a856b372 --- /dev/null +++ b/basics/transaction-v1/anchor/Anchor.toml @@ -0,0 +1,21 @@ +[toolchain] +# Match the repo package manager (pnpm-lock.yaml at root); avoids Anchor's yarn default. +package_manager = "pnpm" +# Agave 4.2 is the first release with the v1 transaction format. Its +# `solana-test-validator` activates the feature at genesis, so a v1 +# transaction that passes these LiteSVM tests can be sent to it as is. +solana_version = "4.2.2" + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +transaction_v1_anchor_program = "F1dMqenFVwcp3SmX9Sq9e22nmcxcact5No12pmb5rL5F" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "cargo test" diff --git a/basics/transaction-v1/anchor/CHANGELOG.md b/basics/transaction-v1/anchor/CHANGELOG.md new file mode 100644 index 000000000..95c8d15fd --- /dev/null +++ b/basics/transaction-v1/anchor/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 2026-09-03 + +Added: a program that stores a 3,000 byte document in one instruction, tested with +LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/anchor/Cargo.toml b/basics/transaction-v1/anchor/Cargo.toml new file mode 100644 index 000000000..16145313a --- /dev/null +++ b/basics/transaction-v1/anchor/Cargo.toml @@ -0,0 +1,23 @@ +# This workspace is deliberately absent from the repository root `Cargo.toml` +# members list, unlike every other `anchor/` example. +# +# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and +# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first +# release that executes v1 transactions, and the two cannot share a lockfile: +# each pins a different `solana-instruction` 3.x patch release. Until the root +# workspace moves to LiteSVM 0.16+, this example resolves on its own, and the +# Anchor v2 workflow (not the root `cargo clippy`) is what keeps it honest. +[workspace] +members = [ + "programs/*" +] +resolver = "2" + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/basics/transaction-v1/anchor/README.md b/basics/transaction-v1/anchor/README.md new file mode 100644 index 000000000..cc6b068bb --- /dev/null +++ b/basics/transaction-v1/anchor/README.md @@ -0,0 +1,56 @@ +# Solana Transaction v1 (Anchor) + +> [!NOTE] +> This is the **Anchor v2** copy of this example. Every `anchor` command on this page +> needs the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.1 --locked` (avm has +> no prebuilt binary for this pre-release). The Anchor v1 version of this example is in +> [`../anchor-v1`](../anchor-v1/). + +A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). Nothing in the program is specific to v1: an Anchor program reads its accounts and instruction data the same way whichever format carried them, and no Anchor change is needed to be called through v1. The tests are where v1 shows up, because the client builds the transaction. + +See also the [Anchor v1](../anchor-v1/), [Pinocchio](../pinocchio/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. + +## Major concepts + +**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. + +**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as the `document: Vec` argument exactly as a 300 byte one would. + +**The document account.** `Document`, a [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer. It is a `BorshAccount` because its `data: Vec` has whatever length the caller chose, which v2's default zero-copy account backing cannot hold. There is no `InitSpace` either: `Document::required_space` sizes the account to each document (discriminator, borsh length prefix, bytes), and the payer pays that [rent](https://solana.com/docs/references/terminology#rent). + +## Lifecycle + +A payer signs a transaction carrying one `store_document` instruction with the document as its argument. The `StoreDocumentAccountConstraints` `init` constraint creates the PDA sized to the document and the handler rejects an empty document (`DocumentError::EmptyDocument`) before copying it in. + +## Setup + +From this directory (`basics/transaction-v1/anchor/`): + +```bash +anchor build +``` + +Prerequisites: [Agave](https://docs.anza.xyz/) CLI 4.2 or later (`Anchor.toml` `[toolchain]` pins 4.2.2, the first line with the v1 format), [Anchor](https://www.anchor-lang.com/docs) v2. + +This example is its own Cargo workspace and is deliberately absent from the repository root workspace's members: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. + +## Testing + +Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm). No local validator. + +```bash +anchor test +``` + +This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example calls it directly rather than through `anchor-v2-testing`, the harness the other Anchor v2 examples use: that harness pins LiteSVM 0.13.1, so `anchor test --profile`, `anchor debugger` and `anchor coverage` see nothing for this example until it moves. The suite covers: + +- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. +- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. +- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. +- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). + +LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. + +## Sending v1 transactions to a real cluster + +The v1 format is behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). It needs Agave 4.2 or later and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. Anchor's TypeScript client (`@coral-xyz/anchor`) builds its transactions with `@solana/web3.js` 1.x, which can read v1 transactions but not build them, so a v1 transaction to an Anchor program is built with `@solana/kit` 8 or later, or in Rust as in the tests here. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the activation status per cluster and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml b/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml new file mode 100644 index 000000000..f2df55f6c --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "transaction-v1-anchor-program" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "transaction_v1_anchor_program" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +anchor-debug = [] +custom-heap = [] +custom-panic = [] + +[dependencies] +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" + +[dev-dependencies] +# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the +# compute unit limit, priority fee, loaded-accounts data size limit and heap +# size from the message's transaction config rather than from ComputeBudget +# instructions. +# +# The other Anchor v2 examples get their LiteSVM through `anchor-v2-testing`, +# whose `svm()` also records the traces behind `anchor test --profile`, +# `anchor debugger` and `anchor coverage`. That harness pins LiteSVM to +# exactly 0.13.1, which cannot execute a v1 transaction, so this example uses +# LiteSVM directly and those three tools see nothing for it. Switch back to +# the harness once it moves to LiteSVM 0.16 or later. +litesvm = "0.16.0" +borsh = "1.6.1" +solana-keypair = "3.1" +# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 +# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. +solana-message = "4.2" +solana-native-token = "3.0" +# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. +solana-packet = "4.2" +solana-transaction = "4.1" +solana-transaction-error = "3.3" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml b/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs new file mode 100644 index 000000000..89cbfd5e6 --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs @@ -0,0 +1,3 @@ +pub mod store; + +pub use store::*; diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs new file mode 100644 index 000000000..337709a4b --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs @@ -0,0 +1,37 @@ +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more +// than one generated item, and only the one evaluating the constraints below +// reads them, so the binding looks unused to rustc even though `space` uses it. +#![allow(unused_variables)] + +use crate::{state::Document, DocumentError}; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +#[instruction(document: Vec)] +pub struct StoreDocumentAccountConstraints { + #[account(mut)] + pub payer: Signer, + + /// Sized to this document exactly, so the rent matches what was stored. + #[account( + init, + payer = payer, + space = Document::required_space(document.len()), + seeds = [ + Document::SEED_PREFIX, + payer.address().as_ref(), + ], + bump, + )] + pub document_account: BorshAccount, + pub system_program: Program, +} + +pub fn handle_store_document( + context: &mut Context, + document: Vec, +) -> Result<()> { + require!(!document.is_empty(), DocumentError::EmptyDocument); + context.accounts.document_account.data = document; + Ok(()) +} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs new file mode 100644 index 000000000..534d8b3fb --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs @@ -0,0 +1,35 @@ +//! Store a document of arbitrary size in a program-owned account, in one +//! instruction. +//! +//! The program itself has nothing to do with transaction v1: it reads the +//! accounts and instruction data it is given like any other program, and does +//! not know or care which transaction format carried them. What v1 changes is +//! how much a single instruction can carry: a 3,000 byte document is beyond +//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it +//! in a v1 transaction (up to 4,096 bytes) instead. + +use anchor_lang::prelude::*; +use instructions::*; + +pub mod instructions; +pub mod state; + +declare_id!("F1dMqenFVwcp3SmX9Sq9e22nmcxcact5No12pmb5rL5F"); + +#[program] +pub mod transaction_v1_anchor_program { + use super::*; + + pub fn store_document( + context: &mut Context, + document: Vec, + ) -> Result<()> { + store::handle_store_document(context, document) + } +} + +#[error_code] +pub enum DocumentError { + #[msg("A document cannot be empty")] + EmptyDocument, +} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs new file mode 100644 index 000000000..2e22c50fb --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs @@ -0,0 +1,19 @@ +use anchor_lang::prelude::*; + +// `borsh` because the struct holds a `Vec` whose length the caller +// chooses: v2's default `#[account]` backing is zero-copy and needs a `Pod` +// (fixed-layout) type. There is deliberately no `InitSpace` either: the +// account is sized to each document rather than to a compile-time maximum. +#[account(borsh)] +pub struct Document { + pub data: Vec, +} + +impl Document { + pub const SEED_PREFIX: &'static [u8; 8] = b"document"; + + /// Discriminator, then the borsh length prefix, then the bytes. + pub fn required_space(document_len: usize) -> usize { + Self::DISCRIMINATOR.len() + 4 + document_len + } +} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs new file mode 100644 index 000000000..d0889e2ad --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod document; + +pub use document::*; diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs b/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs new file mode 100644 index 000000000..61299e4ef --- /dev/null +++ b/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs @@ -0,0 +1,238 @@ +//! The program is exercised with both transaction formats: legacy for a small +//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also +//! cover the part of the format that is easy to get wrong: the transaction +//! config that replaces ComputeBudget instructions, whose unset fields mean +//! zero rather than "use the default". + +use { + anchor_lang::{ + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, + }, + borsh::BorshDeserialize, + litesvm::LiteSVM, + solana_keypair::{Keypair, Signer}, + solana_message::{v1, VersionedMessage}, + solana_native_token::LAMPORTS_PER_SOL, + solana_packet::PACKET_DATA_SIZE, + solana_transaction::{versioned::VersionedTransaction, Transaction}, + solana_transaction_error::TransactionError, + transaction_v1_anchor_program::state::Document, +}; + +/// What LiteSVM charges per signature, and what mainnet has charged since +/// genesis. +const BASE_FEE_LAMPORTS: u64 = 5_000; + +/// Enough for the account creation and the copy. Unlike a legacy transaction, +/// a v1 transaction has no 200,000 CU default to fall back on: leaving the +/// limit unset means zero. +const COMPUTE_UNIT_LIMIT: u32 = 50_000; + +/// The payer, the new account, the system program and the program itself +/// together load well under 64 KiB. Like the compute unit limit, this defaults +/// to zero when unset. +const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; + +/// A v1 transaction's config carries the resource limits that legacy and v0 +/// transactions express with ComputeBudget program instructions. Every test +/// that expects to succeed starts from this one. +fn transaction_config() -> v1::TransactionConfig { + v1::TransactionConfig::empty() + .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) +} + +/// The account as Anchor lays it out: discriminator, then the borsh `Vec`. +#[derive(BorshDeserialize)] +struct DocumentAccount { + _discriminator: [u8; 8], + data: Vec, +} + +fn setup() -> (LiteSVM, Address, Keypair) { + let program_id = transaction_v1_anchor_program::id(); + let mut svm = LiteSVM::new(); + let program_bytes = include_bytes!("../../../target/deploy/transaction_v1_anchor_program.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); + (svm, program_id, payer) +} + +/// The instruction and the PDA it will create. +fn store_document( + program_id: &Address, + payer: &Address, + document: &[u8], +) -> (Instruction, Address) { + let (document_address, _bump) = + Address::find_program_address(&[Document::SEED_PREFIX, payer.as_ref()], program_id); + let instruction = Instruction::new_with_bytes( + *program_id, + &transaction_v1_anchor_program::instruction::StoreDocument { + document: document.to_vec(), + } + .data(), + transaction_v1_anchor_program::accounts::StoreDocumentAccountConstraints { + payer: *payer, + document_account: document_address, + system_program: system_program::ID, + } + .to_account_metas(None), + ); + (instruction, document_address) +} + +/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 +/// counterpart of `Message::new_with_blockhash`; the config rides in the +/// message header, so there is no ComputeBudget instruction to append. +fn v1_transaction( + svm: &LiteSVM, + payer: &Keypair, + instructions: &[Instruction], + config: v1::TransactionConfig, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + &payer.pubkey(), + instructions, + svm.latest_blockhash(), + config, + ) + .unwrap(); + VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() +} + +/// The bytes that would be sent to an RPC node. +fn wire_size(transaction: &VersionedTransaction) -> usize { + wincode::serialize(transaction).unwrap().len() +} + +/// A document with a recognizable pattern, so a partial or shifted copy +/// would not pass the equality check. +fn sample_document(length: usize) -> Vec { + (0..length).map(|i| (i % 251) as u8).collect() +} + +fn stored_document(svm: &LiteSVM, document_address: &Address) -> Vec { + let account = svm.get_account(document_address).unwrap(); + DocumentAccount::try_from_slice(&account.data).unwrap().data +} + +#[test] +fn stores_a_document_too_large_for_a_legacy_transaction() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); + + // Over the legacy limit, under the v1 one. LiteSVM does not enforce either + // (a validator's packet layer does), so the test checks the size itself. + let size = wire_size(&transaction); + assert!( + size > PACKET_DATA_SIZE, + "{size} bytes fits a legacy transaction" + ); + assert!( + size <= v1::MAX_TRANSACTION_SIZE, + "{size} bytes is over the v1 limit" + ); + + svm.send_transaction(transaction).unwrap(); + + let account = svm.get_account(&document_address).unwrap(); + assert_eq!(account.owner, program_id); + assert_eq!(account.data.len(), Document::required_space(document.len())); + assert_eq!(stored_document(&svm, &document_address), document); +} + +#[test] +fn a_legacy_transaction_still_works_for_a_small_document() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + // Nothing about the program changed: the same instruction goes through the + // legacy format when it fits. + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&payer.pubkey()), + &[&payer], + svm.latest_blockhash(), + ); + assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); + + svm.send_transaction(transaction).unwrap(); + + assert_eq!(stored_document(&svm, &document_address), document); +} + +#[test] +fn the_transaction_config_replaces_compute_budget_instructions() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + let rent = svm.minimum_balance_for_rent_exemption(Document::required_space(document.len())); + + // A v1 priority fee is a total in lamports, not a price per compute unit. + let priority_fee_lamports = 10_000; + let config = transaction_config().with_priority_fee(priority_fee_lamports); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + + // v1 moved the signatures to the end, so the first byte on the wire is the + // message version rather than a signature count. 0x81 means v1. + assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); + + let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); + let metadata = svm.send_transaction(transaction).unwrap(); + let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); + + assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); + assert_eq!( + balance_before - balance_after, + rent + BASE_FEE_LAMPORTS + priority_fee_lamports + ); +} + +#[test] +fn unset_config_fields_mean_zero_not_the_default() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + + // No loaded-accounts data size limit: the transaction fails before any + // program runs, because zero bytes of account data may be loaded. + let transaction = v1_transaction( + &svm, + &payer, + std::slice::from_ref(&instruction), + v1::TransactionConfig::empty(), + ); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert_eq!( + failure.err, + TransactionError::MaxLoadedAccountsDataSizeExceeded + ); + + // Data size limit set, compute unit limit not: the program starts with a + // budget of zero and fails on its first instruction. + let config = v1::TransactionConfig::empty() + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert!(matches!( + failure.err, + TransactionError::InstructionError(0, _) + )); + assert!( + failure + .meta + .logs + .iter() + .any(|log| log.contains("exceeded CUs")), + "{:?}", + failure.meta.logs + ); +} diff --git a/basics/transaction-v1/native/CHANGELOG.md b/basics/transaction-v1/native/CHANGELOG.md new file mode 100644 index 000000000..95c8d15fd --- /dev/null +++ b/basics/transaction-v1/native/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 2026-09-03 + +Added: a program that stores a 3,000 byte document in one instruction, tested with +LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/native/Cargo.toml b/basics/transaction-v1/native/Cargo.toml new file mode 100644 index 000000000..ba4d78ae2 --- /dev/null +++ b/basics/transaction-v1/native/Cargo.toml @@ -0,0 +1,13 @@ +# Standalone workspace, deliberately outside the repository root workspace. +# +# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and +# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first +# release that executes v1 transactions, and the two cannot share a lockfile: +# each pins a different `solana-instruction` 3.x patch release. Until the root +# workspace moves to LiteSVM 0.16+, this example resolves on its own. +[workspace] +members = ["program"] +resolver = "2" + +[profile.release] +overflow-checks = true diff --git a/basics/transaction-v1/native/README.md b/basics/transaction-v1/native/README.md new file mode 100644 index 000000000..97defe121 --- /dev/null +++ b/basics/transaction-v1/native/README.md @@ -0,0 +1,52 @@ +# Solana Transaction v1 (Native) + +A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). The program is written directly against `solana-program`, with no framework. Nothing in the program is specific to v1: it reads its accounts and instruction data the same way whichever format carried them. The tests are where v1 shows up, because the client builds the transaction. + +See also the [Anchor v2](../anchor/), [Anchor v1](../anchor-v1/) and [Pinocchio](../pinocchio/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. + +## Major concepts + +**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. + +**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as instruction data exactly as a 300 byte one would. + +**The document account.** A [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer, created by the program with a size equal to the document and owned by the program. The payer pays the [rent](https://solana.com/docs/references/terminology#rent). + +## Lifecycle + +A payer signs a transaction carrying one instruction: the `STORE_DOCUMENT` discriminator byte followed by the document. The `store_document` handler checks that the payer signed, that the document is not empty, that the third account is the System Program, and that the second account is the payer's document PDA. It then creates the PDA through a signed CPI to the System Program, sized to the document, and copies the document in. + +## Setup + +Prerequisites: the [Agave](https://docs.anza.xyz/) toolchain (`cargo build-sbf`) and Rust. + +From this directory (`basics/transaction-v1/native/`): + +```bash +cargo build-sbf --manifest-path=./program/Cargo.toml +``` + +This writes the program binary to `target/deploy/transaction_v1_native_program.so`. + +This example is its own Cargo workspace rather than a member of the repository root workspace: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. + +## Testing + +The Rust + [LiteSVM](https://www.litesvm.com/) tests load the `.so` built above. The binary is embedded at test-compile time, so rebuild after every program change or a stale `.so` silently tests old code. After building, run: + +```bash +cargo test --manifest-path=./program/Cargo.toml +``` + +LiteSVM 0.16.0 is the first release that executes v1 transactions. The suite covers: + +- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. +- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. +- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. +- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). + +LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. + +## Sending v1 transactions to a real cluster + +The v1 format is behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). It needs Agave 4.2 or later and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the activation status per cluster and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/native/program/Cargo.toml b/basics/transaction-v1/native/program/Cargo.toml new file mode 100644 index 000000000..04c066c46 --- /dev/null +++ b/basics/transaction-v1/native/program/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "transaction-v1-native-program" +version = "0.1.0" +edition = "2021" + +[dependencies] +solana-program = "4.0" +solana-system-interface = { version = "2.0.0", features = ["bincode"] } + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } + +[dev-dependencies] +# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the +# compute unit limit, priority fee, loaded-accounts data size limit and heap +# size from the message's transaction config rather than from ComputeBudget +# instructions. +litesvm = "0.16.0" +solana-instruction = "3.4" +solana-keypair = "3.1" +# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 +# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. +solana-message = "4.2" +solana-native-token = "3.0" +# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. +solana-packet = "4.2" +solana-pubkey = "3.0" +solana-transaction = "4.1" +solana-transaction-error = "3.3" +# The SDK's serializer. `wincode::serialize` on a `VersionedTransaction` gives +# the exact bytes that would go over the wire, which is how the tests measure +# transaction size. +wincode = "0.5" diff --git a/basics/transaction-v1/native/program/src/lib.rs b/basics/transaction-v1/native/program/src/lib.rs new file mode 100644 index 000000000..128fdfd7b --- /dev/null +++ b/basics/transaction-v1/native/program/src/lib.rs @@ -0,0 +1,88 @@ +//! Store a document of arbitrary size in a program-owned account, in one +//! instruction. +//! +//! The program itself has nothing to do with transaction v1: it reads the +//! accounts and instruction data it is given like any other program, and does +//! not know or care which transaction format carried them. What v1 changes is +//! how much a single instruction can carry: a 3,000 byte document is beyond +//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it +//! in a v1 transaction (up to 4,096 bytes) instead. + +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint, + entrypoint::ProgramResult, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + sysvar::Sysvar, +}; + +/// First byte of the instruction data. Everything after it is the document. +pub const STORE_DOCUMENT: u8 = 0; + +/// Seed prefix of the document account. One document per payer. +pub const DOCUMENT_SEED: &[u8] = b"document"; + +entrypoint!(process_instruction); + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + match instruction_data.split_first() { + Some((&STORE_DOCUMENT, document)) => store_document(program_id, accounts, document), + _ => Err(ProgramError::InvalidInstructionData), + } +} + +/// Create the payer's document PDA, sized to the document exactly, and copy +/// the document into it. Rent comes from the payer. +fn store_document(program_id: &Pubkey, accounts: &[AccountInfo], document: &[u8]) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let payer = next_account_info(accounts_iter)?; + let document_account = next_account_info(accounts_iter)?; + let system_program = next_account_info(accounts_iter)?; + + if !payer.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if document.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + if !solana_system_interface::program::check_id(system_program.key) { + return Err(ProgramError::IncorrectProgramId); + } + + // The client passes the PDA; derive it here so nobody can hand us some + // other account to write into. + let (expected_address, bump) = + Pubkey::find_program_address(&[DOCUMENT_SEED, payer.key.as_ref()], program_id); + if *document_account.key != expected_address { + return Err(ProgramError::InvalidSeeds); + } + + let lamports_required = Rent::get()?.minimum_balance(document.len()); + + invoke_signed( + &solana_system_interface::instruction::create_account( + payer.key, + document_account.key, + lamports_required, + document.len() as u64, + program_id, + ), + &[ + payer.clone(), + document_account.clone(), + system_program.clone(), + ], + &[&[DOCUMENT_SEED, payer.key.as_ref(), &[bump]]], + )?; + + document_account.data.borrow_mut().copy_from_slice(document); + + Ok(()) +} diff --git a/basics/transaction-v1/native/program/tests/test_transaction_v1.rs b/basics/transaction-v1/native/program/tests/test_transaction_v1.rs new file mode 100644 index 000000000..41010f8cd --- /dev/null +++ b/basics/transaction-v1/native/program/tests/test_transaction_v1.rs @@ -0,0 +1,221 @@ +//! The program is exercised with both transaction formats: legacy for a small +//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also +//! cover the part of the format that is easy to get wrong: the transaction +//! config that replaces ComputeBudget instructions, whose unset fields mean +//! zero rather than "use the default". + +use { + litesvm::LiteSVM, + solana_instruction::{AccountMeta, Instruction}, + solana_keypair::{Keypair, Signer}, + solana_message::{v1, VersionedMessage}, + solana_native_token::LAMPORTS_PER_SOL, + solana_packet::PACKET_DATA_SIZE, + solana_pubkey::Pubkey, + solana_transaction::{versioned::VersionedTransaction, Transaction}, + solana_transaction_error::TransactionError, + transaction_v1_native_program::{DOCUMENT_SEED, STORE_DOCUMENT}, +}; + +/// What LiteSVM charges per signature, and what mainnet has charged since +/// genesis. +const BASE_FEE_LAMPORTS: u64 = 5_000; + +/// Enough for `create_account` plus the copy. Unlike a legacy transaction, a +/// v1 transaction has no 200,000 CU default to fall back on: leaving the +/// limit unset means zero. +const COMPUTE_UNIT_LIMIT: u32 = 20_000; + +/// The payer, the new account, the system program and the program itself +/// together load well under 64 KiB. Like the compute unit limit, this defaults +/// to zero when unset. +const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; + +/// A v1 transaction's config carries the resource limits that legacy and v0 +/// transactions express with ComputeBudget program instructions. Every test +/// that expects to succeed starts from this one. +fn transaction_config() -> v1::TransactionConfig { + v1::TransactionConfig::empty() + .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) +} + +fn setup() -> (LiteSVM, Pubkey, Keypair) { + let mut svm = LiteSVM::new(); + let program_id = Pubkey::new_unique(); + // The .so is built into the workspace target/deploy by + // `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the + // project root). Rebuild after every program change: the binary is + // embedded at test-compile time, so a stale .so silently tests old code. + let program_bytes = include_bytes!("../../target/deploy/transaction_v1_native_program.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); + (svm, program_id, payer) +} + +/// The instruction and the PDA it will create. +fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Instruction, Pubkey) { + let (document_address, _bump) = + Pubkey::find_program_address(&[DOCUMENT_SEED, payer.as_ref()], program_id); + let mut data = Vec::with_capacity(1 + document.len()); + data.push(STORE_DOCUMENT); + data.extend_from_slice(document); + let instruction = Instruction { + program_id: *program_id, + accounts: vec![ + AccountMeta::new(*payer, true), + AccountMeta::new(document_address, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + data, + }; + (instruction, document_address) +} + +/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 +/// counterpart of `Message::new_with_blockhash`; the config rides in the +/// message header, so there is no ComputeBudget instruction to append. +fn v1_transaction( + svm: &LiteSVM, + payer: &Keypair, + instructions: &[Instruction], + config: v1::TransactionConfig, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + &payer.pubkey(), + instructions, + svm.latest_blockhash(), + config, + ) + .unwrap(); + VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() +} + +/// The bytes that would be sent to an RPC node. +fn wire_size(transaction: &VersionedTransaction) -> usize { + wincode::serialize(transaction).unwrap().len() +} + +/// A document with a recognizable pattern, so a partial or shifted copy +/// would not pass the equality check. +fn sample_document(length: usize) -> Vec { + (0..length).map(|i| (i % 251) as u8).collect() +} + +#[test] +fn stores_a_document_too_large_for_a_legacy_transaction() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); + + // Over the legacy limit, under the v1 one. LiteSVM does not enforce either + // (a validator's packet layer does), so the test checks the size itself. + let size = wire_size(&transaction); + assert!( + size > PACKET_DATA_SIZE, + "{size} bytes fits a legacy transaction" + ); + assert!( + size <= v1::MAX_TRANSACTION_SIZE, + "{size} bytes is over the v1 limit" + ); + + svm.send_transaction(transaction).unwrap(); + + let account = svm.get_account(&document_address).unwrap(); + assert_eq!(account.owner, program_id); + assert_eq!(account.data, document); +} + +#[test] +fn a_legacy_transaction_still_works_for_a_small_document() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + // Nothing about the program changed: the same instruction goes through the + // legacy format when it fits. + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&payer.pubkey()), + &[&payer], + svm.latest_blockhash(), + ); + assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); + + svm.send_transaction(transaction).unwrap(); + + assert_eq!(svm.get_account(&document_address).unwrap().data, document); +} + +#[test] +fn the_transaction_config_replaces_compute_budget_instructions() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + let rent = svm.minimum_balance_for_rent_exemption(document.len()); + + // A v1 priority fee is a total in lamports, not a price per compute unit. + let priority_fee_lamports = 10_000; + let config = transaction_config().with_priority_fee(priority_fee_lamports); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + + // v1 moved the signatures to the end, so the first byte on the wire is the + // message version rather than a signature count. 0x81 means v1. + assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); + + let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); + let metadata = svm.send_transaction(transaction).unwrap(); + let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); + + assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); + assert_eq!( + balance_before - balance_after, + rent + BASE_FEE_LAMPORTS + priority_fee_lamports + ); +} + +#[test] +fn unset_config_fields_mean_zero_not_the_default() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + + // No loaded-accounts data size limit: the transaction fails before any + // program runs, because zero bytes of account data may be loaded. + let transaction = v1_transaction( + &svm, + &payer, + std::slice::from_ref(&instruction), + v1::TransactionConfig::empty(), + ); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert_eq!( + failure.err, + TransactionError::MaxLoadedAccountsDataSizeExceeded + ); + + // Data size limit set, compute unit limit not: the program starts with a + // budget of zero and fails on its first instruction. + let config = v1::TransactionConfig::empty() + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert!(matches!( + failure.err, + TransactionError::InstructionError(0, _) + )); + assert!( + failure + .meta + .logs + .iter() + .any(|log| log.contains("exceeded CUs")), + "{:?}", + failure.meta.logs + ); +} diff --git a/basics/transaction-v1/pinocchio/CHANGELOG.md b/basics/transaction-v1/pinocchio/CHANGELOG.md new file mode 100644 index 000000000..95c8d15fd --- /dev/null +++ b/basics/transaction-v1/pinocchio/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 2026-09-03 + +Added: a program that stores a 3,000 byte document in one instruction, tested with +LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/pinocchio/Cargo.toml b/basics/transaction-v1/pinocchio/Cargo.toml new file mode 100644 index 000000000..ba4d78ae2 --- /dev/null +++ b/basics/transaction-v1/pinocchio/Cargo.toml @@ -0,0 +1,13 @@ +# Standalone workspace, deliberately outside the repository root workspace. +# +# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and +# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first +# release that executes v1 transactions, and the two cannot share a lockfile: +# each pins a different `solana-instruction` 3.x patch release. Until the root +# workspace moves to LiteSVM 0.16+, this example resolves on its own. +[workspace] +members = ["program"] +resolver = "2" + +[profile.release] +overflow-checks = true diff --git a/basics/transaction-v1/pinocchio/README.md b/basics/transaction-v1/pinocchio/README.md new file mode 100644 index 000000000..65d62e937 --- /dev/null +++ b/basics/transaction-v1/pinocchio/README.md @@ -0,0 +1,52 @@ +# Solana Transaction v1 (Pinocchio) + +A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). The program is written with [Pinocchio](https://github.com/anza-xyz/pinocchio), the zero-copy, zero-allocation program library. Nothing in the program is specific to v1: it reads its accounts and instruction data the same way whichever format carried them. The tests are where v1 shows up, because the client builds the transaction. + +See also the [Anchor v2](../anchor/), [Anchor v1](../anchor-v1/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. + +## Major concepts + +**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. + +**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as instruction data exactly as a 300 byte one would. + +**The document account.** A [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer, created by the program with a size equal to the document and owned by the program. The payer pays the [rent](https://solana.com/docs/references/terminology#rent). + +## Lifecycle + +A payer signs a transaction carrying one instruction: the `STORE_DOCUMENT` discriminator byte followed by the document. The `store_document` handler checks that the payer signed, that the document is not empty, that the third account is the System Program, and that the second account is the payer's document PDA. It then creates the PDA through a signed CPI to the System Program, sized to the document, and copies the document in. + +## Setup + +Prerequisites: the [Agave](https://docs.anza.xyz/) toolchain (`cargo build-sbf`) and Rust. + +From this directory (`basics/transaction-v1/pinocchio/`): + +```bash +cargo build-sbf --manifest-path=./program/Cargo.toml +``` + +This writes the program binary to `target/deploy/transaction_v1_pinocchio_program.so`. + +This example is its own Cargo workspace rather than a member of the repository root workspace: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. + +## Testing + +The Rust + [LiteSVM](https://www.litesvm.com/) tests load the `.so` built above. The binary is embedded at test-compile time, so rebuild after every program change or a stale `.so` silently tests old code. After building, run: + +```bash +cargo test --manifest-path=./program/Cargo.toml +``` + +LiteSVM 0.16.0 is the first release that executes v1 transactions. The suite covers: + +- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. +- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. +- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. +- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). + +LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. + +## Sending v1 transactions to a real cluster + +The v1 format is behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). It needs Agave 4.2 or later and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the activation status per cluster and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/pinocchio/program/Cargo.toml b/basics/transaction-v1/pinocchio/program/Cargo.toml new file mode 100644 index 000000000..dc98ef043 --- /dev/null +++ b/basics/transaction-v1/pinocchio/program/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "transaction-v1-pinocchio-program" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio = { version = "0.10.2", features = ["cpi"] } +pinocchio-system = "0.5.0" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } + +[dev-dependencies] +# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the +# compute unit limit, priority fee, loaded-accounts data size limit and heap +# size from the message's transaction config rather than from ComputeBudget +# instructions. +litesvm = "0.16.0" +solana-instruction = "3.4" +solana-keypair = "3.1" +# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 +# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. +solana-message = "4.2" +solana-native-token = "3.0" +# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. +solana-packet = "4.2" +solana-pubkey = "3.0" +solana-system-interface = "2.0.0" +solana-transaction = "4.1" +solana-transaction-error = "3.3" +# The SDK's serializer. `wincode::serialize` on a `VersionedTransaction` gives +# the exact bytes that would go over the wire, which is how the tests measure +# transaction size. +wincode = "0.5" diff --git a/basics/transaction-v1/pinocchio/program/src/lib.rs b/basics/transaction-v1/pinocchio/program/src/lib.rs new file mode 100644 index 000000000..7045885a7 --- /dev/null +++ b/basics/transaction-v1/pinocchio/program/src/lib.rs @@ -0,0 +1,94 @@ +//! Store a document of arbitrary size in a program-owned account, in one +//! instruction. +//! +//! The program itself has nothing to do with transaction v1: it reads the +//! accounts and instruction data it is given like any other program, and does +//! not know or care which transaction format carried them. What v1 changes is +//! how much a single instruction can carry: a 3,000 byte document is beyond +//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it +//! in a v1 transaction (up to 4,096 bytes) instead. + +#![no_std] + +use pinocchio::{ + cpi::{Seed, Signer}, + entrypoint, + error::ProgramError, + nostd_panic_handler, + sysvars::{rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_system::instructions::CreateAccount; + +entrypoint!(process_instruction); +nostd_panic_handler!(); + +/// First byte of the instruction data. Everything after it is the document. +pub const STORE_DOCUMENT: u8 = 0; + +/// Seed prefix of the document account. One document per payer. +pub const DOCUMENT_SEED: &[u8] = b"document"; + +fn process_instruction( + program_id: &Address, + accounts: &[AccountView], + instruction_data: &[u8], +) -> ProgramResult { + match instruction_data.split_first() { + Some((&STORE_DOCUMENT, document)) => store_document(program_id, accounts, document), + _ => Err(ProgramError::InvalidInstructionData), + } +} + +/// Create the payer's document PDA, sized to the document exactly, and copy +/// the document into it. Rent comes from the payer. +fn store_document( + program_id: &Address, + accounts: &[AccountView], + document: &[u8], +) -> ProgramResult { + let [payer, document_account, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if document.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + if !pinocchio_system::check_id(system_program.address()) { + return Err(ProgramError::IncorrectProgramId); + } + + // The client passes the PDA; derive it here so nobody can hand us some + // other account to write into. + let (expected_address, bump) = + Address::find_program_address(&[DOCUMENT_SEED, payer.address().as_ref()], program_id); + if document_account.address() != &expected_address { + return Err(ProgramError::InvalidSeeds); + } + + let lamports_required = Rent::get()?.try_minimum_balance(document.len())?; + + let bump_bytes = [bump]; + let seeds = [ + Seed::from(DOCUMENT_SEED), + Seed::from(payer.address().as_ref()), + Seed::from(&bump_bytes), + ]; + let signers = [Signer::from(&seeds)]; + + CreateAccount { + from: payer, + to: document_account, + lamports: lamports_required, + space: document.len() as u64, + owner: program_id, + } + .invoke_signed(&signers)?; + + document_account.try_borrow_mut()?.copy_from_slice(document); + + Ok(()) +} diff --git a/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs b/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs new file mode 100644 index 000000000..0e282c711 --- /dev/null +++ b/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs @@ -0,0 +1,221 @@ +//! The program is exercised with both transaction formats: legacy for a small +//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also +//! cover the part of the format that is easy to get wrong: the transaction +//! config that replaces ComputeBudget instructions, whose unset fields mean +//! zero rather than "use the default". + +use { + litesvm::LiteSVM, + solana_instruction::{AccountMeta, Instruction}, + solana_keypair::{Keypair, Signer}, + solana_message::{v1, VersionedMessage}, + solana_native_token::LAMPORTS_PER_SOL, + solana_packet::PACKET_DATA_SIZE, + solana_pubkey::Pubkey, + solana_transaction::{versioned::VersionedTransaction, Transaction}, + solana_transaction_error::TransactionError, + transaction_v1_pinocchio_program::{DOCUMENT_SEED, STORE_DOCUMENT}, +}; + +/// What LiteSVM charges per signature, and what mainnet has charged since +/// genesis. +const BASE_FEE_LAMPORTS: u64 = 5_000; + +/// Enough for `create_account` plus the copy. Unlike a legacy transaction, a +/// v1 transaction has no 200,000 CU default to fall back on: leaving the +/// limit unset means zero. +const COMPUTE_UNIT_LIMIT: u32 = 20_000; + +/// The payer, the new account, the system program and the program itself +/// together load well under 64 KiB. Like the compute unit limit, this defaults +/// to zero when unset. +const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; + +/// A v1 transaction's config carries the resource limits that legacy and v0 +/// transactions express with ComputeBudget program instructions. Every test +/// that expects to succeed starts from this one. +fn transaction_config() -> v1::TransactionConfig { + v1::TransactionConfig::empty() + .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) +} + +fn setup() -> (LiteSVM, Pubkey, Keypair) { + let mut svm = LiteSVM::new(); + let program_id = Pubkey::new_unique(); + // The .so is built into the workspace target/deploy by + // `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the + // project root). Rebuild after every program change: the binary is + // embedded at test-compile time, so a stale .so silently tests old code. + let program_bytes = include_bytes!("../../target/deploy/transaction_v1_pinocchio_program.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); + (svm, program_id, payer) +} + +/// The instruction and the PDA it will create. +fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Instruction, Pubkey) { + let (document_address, _bump) = + Pubkey::find_program_address(&[DOCUMENT_SEED, payer.as_ref()], program_id); + let mut data = Vec::with_capacity(1 + document.len()); + data.push(STORE_DOCUMENT); + data.extend_from_slice(document); + let instruction = Instruction { + program_id: *program_id, + accounts: vec![ + AccountMeta::new(*payer, true), + AccountMeta::new(document_address, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + data, + }; + (instruction, document_address) +} + +/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 +/// counterpart of `Message::new_with_blockhash`; the config rides in the +/// message header, so there is no ComputeBudget instruction to append. +fn v1_transaction( + svm: &LiteSVM, + payer: &Keypair, + instructions: &[Instruction], + config: v1::TransactionConfig, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + &payer.pubkey(), + instructions, + svm.latest_blockhash(), + config, + ) + .unwrap(); + VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() +} + +/// The bytes that would be sent to an RPC node. +fn wire_size(transaction: &VersionedTransaction) -> usize { + wincode::serialize(transaction).unwrap().len() +} + +/// A document with a recognizable pattern, so a partial or shifted copy +/// would not pass the equality check. +fn sample_document(length: usize) -> Vec { + (0..length).map(|i| (i % 251) as u8).collect() +} + +#[test] +fn stores_a_document_too_large_for_a_legacy_transaction() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); + + // Over the legacy limit, under the v1 one. LiteSVM does not enforce either + // (a validator's packet layer does), so the test checks the size itself. + let size = wire_size(&transaction); + assert!( + size > PACKET_DATA_SIZE, + "{size} bytes fits a legacy transaction" + ); + assert!( + size <= v1::MAX_TRANSACTION_SIZE, + "{size} bytes is over the v1 limit" + ); + + svm.send_transaction(transaction).unwrap(); + + let account = svm.get_account(&document_address).unwrap(); + assert_eq!(account.owner, program_id); + assert_eq!(account.data, document); +} + +#[test] +fn a_legacy_transaction_still_works_for_a_small_document() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); + + // Nothing about the program changed: the same instruction goes through the + // legacy format when it fits. + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&payer.pubkey()), + &[&payer], + svm.latest_blockhash(), + ); + assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); + + svm.send_transaction(transaction).unwrap(); + + assert_eq!(svm.get_account(&document_address).unwrap().data, document); +} + +#[test] +fn the_transaction_config_replaces_compute_budget_instructions() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(3_000); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + let rent = svm.minimum_balance_for_rent_exemption(document.len()); + + // A v1 priority fee is a total in lamports, not a price per compute unit. + let priority_fee_lamports = 10_000; + let config = transaction_config().with_priority_fee(priority_fee_lamports); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + + // v1 moved the signatures to the end, so the first byte on the wire is the + // message version rather than a signature count. 0x81 means v1. + assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); + + let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); + let metadata = svm.send_transaction(transaction).unwrap(); + let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); + + assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); + assert_eq!( + balance_before - balance_after, + rent + BASE_FEE_LAMPORTS + priority_fee_lamports + ); +} + +#[test] +fn unset_config_fields_mean_zero_not_the_default() { + let (mut svm, program_id, payer) = setup(); + let document = sample_document(500); + let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); + + // No loaded-accounts data size limit: the transaction fails before any + // program runs, because zero bytes of account data may be loaded. + let transaction = v1_transaction( + &svm, + &payer, + std::slice::from_ref(&instruction), + v1::TransactionConfig::empty(), + ); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert_eq!( + failure.err, + TransactionError::MaxLoadedAccountsDataSizeExceeded + ); + + // Data size limit set, compute unit limit not: the program starts with a + // budget of zero and fails on its first instruction. + let config = v1::TransactionConfig::empty() + .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); + let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let failure = svm.send_transaction(transaction).unwrap_err(); + assert!(matches!( + failure.err, + TransactionError::InstructionError(0, _) + )); + assert!( + failure + .meta + .logs + .iter() + .any(|log| log.contains("exceeded CUs")), + "{:?}", + failure.meta.logs + ); +} diff --git a/docs/transaction-v1.md b/docs/transaction-v1.md new file mode 100644 index 000000000..8aae30bd8 --- /dev/null +++ b/docs/transaction-v1.md @@ -0,0 +1,145 @@ +# Transaction v1 and this repository + +Solana's v1 transaction format ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), sized by [SIMD-0296](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md)) raises the maximum transaction size from 1,232 to 4,096 bytes. This page is the repository's view of it: what changes, where it is live, which of the tools these examples build on can send it, and what that means for the examples. The worked example is [`basics/transaction-v1/`](../basics/transaction-v1/anchor/), in Anchor v2, Anchor v1, Pinocchio and native Rust. + +Status is as of 3 September 2026. + +## What changes + +| | Legacy and v0 | v1 | +| --- | --- | --- | +| Maximum size | 1,232 bytes | 4,096 bytes | +| Compute unit limit, heap size, loaded-accounts data size limit | ComputeBudget program instructions | `TransactionConfig` fields in the message header | +| Priority fee | `SetComputeUnitPrice`, in micro-lamports per compute unit | `TransactionConfig::priority_fee`, a total in lamports | +| Unset resource limits | Runtime defaults (200,000 compute units per instruction, 64 MiB of account data) | Zero. Only the heap size falls back, to 32 KiB | +| ComputeBudget instructions present | Applied | Ignored | +| Address lookup tables | v0 only | Not supported | +| Account addresses | Bounded by size (v0: up to 64 with lookup tables) | Up to 64, all inline, no duplicates | +| Instructions | Bounded by size | Up to 64 | +| Signatures | Bounded by size | Up to 12 | +| Wire format | Signature count first | Message version byte first (`0x81`), signatures last | + +Two of these bite in practice. A v1 transaction that does not set its compute unit limit fails on its first instruction, and one that does not set its loaded-accounts data size limit fails before any program runs, with `MaxLoadedAccountsDataSizeExceeded`. The example's `unset_config_fields_mean_zero_not_the_default` test shows both. + +The other one is the lookup tables. v1 is bigger, not more flexible: a transaction that touches more accounts than fit inline in 4,096 bytes still needs v0 and a lookup table. The vault strategy example's deposit, with five accounts per basket asset, is that case. + +## What changes for programs + +Nothing. A program sees the same accounts and the same instruction data whether a legacy, v0 or v1 transaction carried them, so no program in this repository, in any framework, needs a change to be called through v1. The one visible difference is to programs that read the transaction's ComputeBudget instructions through the instructions sysvar, for instance to detect a priority fee: a v1 transaction has none, and the config that replaced them is not exposed to programs. + +## What changes for tests and clients + +Everything that builds a transaction has to know the format. That is the test suites here, and every client. + +### Sending a v1 transaction in Rust + +`solana-message` 4.2 or later has the `v1` module. The example's tests do exactly this against LiteSVM: + +```rust +use solana_message::{v1, VersionedMessage}; +use solana_transaction::versioned::VersionedTransaction; + +let config = v1::TransactionConfig::empty() + .with_compute_unit_limit(20_000) + .with_loaded_accounts_data_size_limit(64 * 1024) + // A total in lamports, not a price per compute unit. + .with_priority_fee(5_000); + +let message = v1::Message::try_compile_with_config( + &payer.pubkey(), + &[instruction], + recent_blockhash, + config, +)?; +let transaction = VersionedTransaction::try_new(VersionedMessage::V1(message), &[&payer])?; +``` + +`wincode::serialize(&transaction)` gives the wire bytes, and `v1::MAX_TRANSACTION_SIZE` (4,096) and `solana_packet::PACKET_DATA_SIZE` (1,232) are the two limits to compare against. LiteSVM enforces neither, so a test that cares about size checks it itself. + +### Sending a v1 transaction in TypeScript + +`@solana/kit` 8.0.0 or later. The config setter replaces the ComputeBudget instruction helpers: + +```typescript +const message = pipe( + createTransactionMessage({ version: 1 }), + (m) => setTransactionMessageFeePayerSigner(payer, m), + (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + (m) => appendTransactionMessageInstruction(instruction, m), + (m) => + setTransactionMessageConfig( + { + computeUnitLimit: 20_000, + loadedAccountsDataSizeLimit: 64 * 1024, + priorityFeeLamports: 5_000n, + }, + m, + ), +); +``` + +Send and simulate with `encoding: "base64"`: base58 encoding stops at 1,232 bytes. + +### Reading v1 transactions + +Every `getTransaction`, `getBlock` and `blockSubscribe` call needs `maxSupportedTransactionVersion: 1` (the integer, not the string). Without it, `getTransaction` returns error `-32015`, `getBlock` fails for the whole block if it holds one v1 transaction, and `blockSubscribe` emits `block: null`. Indexers that scan for ComputeBudget instructions to read fees see zero for v1 transactions and have to read `transactionConfig` instead. + +## Where v1 is live + +The feature gate is `enable_tx_v1`, `txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`, shipped in Agave 4.2. + +| Cluster | Status | +| --- | --- | +| `solana-test-validator` (Agave 4.2+) and Surfpool 1.5+ | Active at genesis | +| Testnet | Active since epoch 1025 | +| Devnet | Pending activation (Agave 4.2.2) | +| Mainnet beta | Pending. Anza's stated target is 9 September 2026 | + +Check a cluster before sending: + +```bash +solana -u feature status txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL +``` + +## Tool support + +What the toolchains this repository uses can do with v1, checked against the versions the examples pin. + +| Tool | Used here as | v1 | +| --- | --- | --- | +| Agave CLI and `solana-test-validator` | CI installs 3.1.14; the example pins 4.2.2 | 4.2.0 and later | +| `solana-message`, `solana-transaction` (Rust) | 3.x in most tests; 4.2 / 4.1 in the example | `solana-message` 4.2 and later | +| [LiteSVM](https://github.com/LiteSVM/litesvm) | 0.13.1 everywhere but the example, which uses 0.16.0 | 0.16.0 (24 August 2026) and later. Reads the compute budget from the config | +| [Surfpool](https://surfpool.run/) | `anchor test` and `anchor localnet` default validator | 1.5 and later | +| `@solana/kit` | Not used by the tests | 8.0.0 and later | +| `@solana/web3.js` 1.x | Two wallet-adapter demo apps | Reads v1 from 1.99.0. Cannot build one. `@solana/web3.js` 3.0.0-rc.3 and later can | +| Anchor v2 programs (`anchor-lang` 2.0.0-rc.1) | 46 examples | No change needed | +| `anchor-v2-testing` (the Anchor v2 LiteSVM harness) | Every Anchor v2 example but this one | Not yet. Pins LiteSVM 0.13.1, on the pinned revision and on the tip of `anchor-next` as of today | +| Anchor v1 programs (`anchor-lang` 1.1.2) | 37 examples | No change needed | +| `@coral-xyz/anchor` 1.1.2 (Anchor's TypeScript client) | Not used by the tests | Not yet. Depends on `@solana/web3.js` 1.x, which cannot build a v1 transaction | +| Quasar programs (`quasar-lang` 0.1.0) | 53 examples | No change needed | +| `quasar-test` and `quasar-svm` (Quasar's test harness and VM) | Every Quasar example | Not yet. `quasar-svm` 0.1.0 is on `solana-message` 3.x and its `master` branch on 4.1, one release short of the `v1` module | +| Pinocchio, native Rust and sBPF assembly programs | 50 examples | No change needed | +| [`solana-kite`](https://github.com/solanakite/kite-rust) 0.4.0 | 111 test suites | Not yet. Pins LiteSVM 0.13.1 | + +## What that means for the examples + +Only `basics/transaction-v1/` sends v1 transactions. Every other example's tests still send legacy transactions, and they keep passing, because nothing on the program side changed. + +Moving the rest of the repository is blocked on the two test dependencies, not on the examples: + +1. `solana-kite` needs a release on LiteSVM 0.16 or later. 111 test suites here depend on it, and LiteSVM 0.13.1 and 0.16.0 cannot share a lockfile (each pins a different `solana-instruction` 3.x patch release), so the root workspace cannot move until it does. +2. `anchor-v2-testing` needs to move off its `=0.13.1` pin. Until then an Anchor v2 example can send v1 transactions only by calling LiteSVM directly, as the example does, and loses `anchor test --profile`, `anchor debugger` and `anchor coverage` for it. +3. Quasar needs `quasar-svm` on `solana-message` 4.2 or later, and a way for a `#[quasar_test]` test to choose the transaction format. Neither exists yet, so there is no Quasar variant of the example. + +That is also why the example's four directories are standalone Cargo workspaces, deliberately absent from the root `Cargo.toml` members list. + +## Sources + +- [Larger transaction sizes](https://solana.com/upgrades/larger-transaction-sizes), the upgrade page. +- [SIMD-0385: Transaction V1](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md) and [SIMD-0296: Larger transactions](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md). +- [solana-foundation/transaction-v1-examples](https://github.com/solana-foundation/transaction-v1-examples), runnable Rust, TypeScript, Python and Go clients and indexers, with a minimum-version table. +- [The `transactions-v1` reference in solana-dev-skill](https://github.com/solana-foundation/solana-dev-skill/blob/main/skills/solana-dev/references/transactions-v1.md). +- [Feature gate tracker schedule](https://github.com/anza-xyz/agave/wiki/Feature-Gate-Tracker-Schedule), for activation status per cluster. +- [LiteSVM 0.16.0](https://github.com/LiteSVM/litesvm/releases/tag/v0.16.0), the release that added v1 support. +- [Solana v1 transactions explained](https://www.quicknode.com/blog/solana-v1-transactions-explained), on the Quicknode blog. diff --git a/llms.txt b/llms.txt index 0092cabb5..e0ff00db8 100644 --- a/llms.txt +++ b/llms.txt @@ -26,3 +26,4 @@ A Solana program is what other chains call a smart contract. These examples are - Tests are Rust integration tests against [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm), run with `cargo test`; no local validator is needed. - Every finance example has a `kani-proofs/` directory with [Kani](https://github.com/model-checking/kani) harnesses proving money-math invariants over all inputs, for example [escrow's proofs](https://github.com/quicknode/solana-program-examples/tree/main/finance/escrow/kani-proofs). +- [Transaction v1](https://github.com/quicknode/solana-program-examples/blob/main/docs/transaction-v1.md): what the 4,096 byte v1 transaction format changes for programs (nothing) and for tests and clients, which tools support it, and the [transaction v1 example](https://github.com/quicknode/solana-program-examples/tree/main/basics/transaction-v1/anchor) whose LiteSVM tests send v1 transactions. From 0441b543296f2016197f74368e6fa16318251eed Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 17:01:45 +0000 Subject: [PATCH 2/4] Send every Anchor v1 test transaction as a v1 transaction Solana's v1 transaction format (SIMD-0385) activated on mainnet beta at epoch 1035. Programs need no change for it; the client that builds the transaction does, and in these examples that is the tests. Each Anchor v1 test directory gains a transaction_v1 module, the same file in all 57 examples. Its send_transaction_from_instructions has the same arguments and error type as solana-kite's, which builds a legacy transaction, so each test file swaps one import and no call site changes. A v1 transaction's unset limits are zero, so the module asks for what a legacy transaction gets by default: 200,000 compute units per instruction up to 1,400,000, and 64 MiB of loaded account data. The six tests that build a transaction by hand use the module's v1_transaction instead. This change is Anchor v1 only. The Anchor v2, Pinocchio and native copies of basics/transaction-v1 and docs/transaction-v1.md leave the branch; they remain in commit 8bec3dc. Anchor v2 and Quasar tests cannot send v1 yet: anchor-v2-testing pins LiteSVM 0.13.1 and quasar-svm is on a solana-message without the v1 module. Kite's token helpers still send their setup transactions as legacy ones, through kite's own sender. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014EotgxjmAGtATDFVnBtjXj --- CHANGELOG.md | 42 ++++ CONTRIBUTING.md | 4 + Cargo.toml | 6 - README.md | 6 +- .../anchor-program-example/Cargo.toml | 2 + .../tests/test_account_data.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-program-example/Cargo.toml | 2 + .../tests/test_checking_accounts.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/close-account/Cargo.toml | 2 + .../close-account/tests/test_close_account.rs | 6 +- .../close-account/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/counter_anchor/Cargo.toml | 2 + .../counter_anchor/tests/test_counter.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/create-system-account/Cargo.toml | 2 + .../tests/test_create_account.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/hand/Cargo.toml | 2 + .../programs/hand/tests/test_hand.rs | 6 +- .../programs/hand/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/lever/Cargo.toml | 2 + .../programs/lever/tests/test_lever.rs | 6 +- .../lever/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/favorites/Cargo.toml | 2 + .../favorites/tests/test_favorites.rs | 6 +- .../favorites/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/hello-solana/Cargo.toml | 2 + .../programs/hello-solana/tests/test_hello.rs | 14 +- .../hello-solana/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-program-example/Cargo.toml | 2 + .../tests/test_pda_rent_payer.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../processing-instructions/Cargo.toml | 2 + .../tests/test_processing_instructions.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-program-example/Cargo.toml | 2 + .../tests/test_program_derived_addresses.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/pythexample/Cargo.toml | 2 + .../programs/pythexample/tests/test_pyth.rs | 6 +- .../pythexample/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/anchor-realloc/Cargo.toml | 2 + .../anchor-realloc/tests/test_realloc.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/rent-example/Cargo.toml | 2 + .../programs/rent-example/tests/test_rent.rs | 6 +- .../rent-example/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/carnival/Cargo.toml | 2 + .../programs/carnival/tests/test_carnival.rs | 6 +- .../carnival/tests/transaction_v1/mod.rs | 94 +++++++ basics/transaction-v1/anchor-v1/CHANGELOG.md | 2 +- basics/transaction-v1/anchor-v1/README.md | 13 +- .../programs/transaction-v1/Cargo.toml | 6 +- basics/transaction-v1/anchor/Anchor.toml | 18 -- basics/transaction-v1/anchor/CHANGELOG.md | 6 - basics/transaction-v1/anchor/Cargo.toml | 23 -- basics/transaction-v1/anchor/README.md | 56 ----- .../anchor/programs/transaction-v1/Cargo.toml | 58 ----- .../anchor/programs/transaction-v1/Xargo.toml | 2 - .../transaction-v1/src/instructions/mod.rs | 3 - .../transaction-v1/src/instructions/store.rs | 37 --- .../anchor/programs/transaction-v1/src/lib.rs | 35 --- .../transaction-v1/src/state/document.rs | 19 -- .../programs/transaction-v1/src/state/mod.rs | 3 - .../tests/test_transaction_v1.rs | 238 ------------------ basics/transaction-v1/native/CHANGELOG.md | 6 - basics/transaction-v1/native/Cargo.toml | 13 - basics/transaction-v1/native/README.md | 52 ---- .../transaction-v1/native/program/Cargo.toml | 40 --- .../transaction-v1/native/program/src/lib.rs | 88 ------- .../program/tests/test_transaction_v1.rs | 221 ---------------- basics/transaction-v1/pinocchio/CHANGELOG.md | 6 - basics/transaction-v1/pinocchio/Cargo.toml | 13 - basics/transaction-v1/pinocchio/README.md | 52 ---- .../pinocchio/program/Cargo.toml | 41 --- .../pinocchio/program/src/lib.rs | 94 ------- .../program/tests/test_transaction_v1.rs | 221 ---------------- .../programs/transfer-sol/Cargo.toml | 2 + .../transfer-sol/tests/test_transfer_sol.rs | 6 +- .../transfer-sol/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/cnft-burn/Cargo.toml | 1 + .../programs/cnft-burn/tests/test_burn.rs | 11 +- .../cnft-burn/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/cnft-vault/Cargo.toml | 1 + .../programs/cnft-vault/tests/test_vault.rs | 11 +- .../cnft-vault/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/cutils/Cargo.toml | 1 + .../programs/cutils/tests/test_cutils.rs | 11 +- .../cutils/tests/transaction_v1/mod.rs | 94 +++++++ docs/transaction-v1.md | 146 ----------- .../programs/betting-market/Cargo.toml | 2 + .../tests/test_betting_market.rs | 7 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/escrow/Cargo.toml | 2 + .../programs/escrow/tests/test_escrow.rs | 5 +- .../escrow/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/fundraiser/Cargo.toml | 2 + .../fundraiser/tests/test_fundraiser.rs | 5 +- .../fundraiser/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/lending/Cargo.toml | 2 + .../programs/lending/tests/common/mod.rs | 6 +- .../lending/tests/common/transaction_v1.rs | 94 +++++++ .../programs/lending/tests/test_security.rs | 2 +- .../programs/managed-fund/Cargo.toml | 2 + .../managed-fund/tests/managed_fund.rs | 5 +- .../managed-fund/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/options/tests/test_options.rs | 16 +- .../options/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/order-book/Cargo.toml | 1 + .../order-book/tests/test_order_book.rs | 12 +- .../order-book/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/perpetual-futures/Cargo.toml | 2 + .../tests/test_perpetual_futures.rs | 5 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/prop-amm/Cargo.toml | 2 + .../programs/prop-amm/tests/test_prop_amm.rs | 5 +- .../prop-amm/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/token-swap/Cargo.toml | 2 + .../programs/token-swap/tests/test_swap.rs | 5 +- .../token-swap/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/create-token/Cargo.toml | 2 + .../create-token/tests/test_create_token.rs | 6 +- .../create-token/tests/transaction_v1/mod.rs | 94 +++++++ .../external-delegate-token-master/Cargo.toml | 2 + .../tests/test_external_delegate.rs | 5 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/nft-minter/Cargo.toml | 2 + .../nft-minter/tests/test_nft_minter.rs | 6 +- .../nft-minter/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/mint-nft/Cargo.toml | 2 + .../mint-nft/tests/test_nft_operations.rs | 6 +- .../mint-nft/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/token-minter/Cargo.toml | 2 + .../token-minter/tests/test_pda_mint.rs | 6 +- .../token-minter/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/basics/Cargo.toml | 2 + .../programs/basics/tests/test_basics.rs | 6 +- .../basics/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/cpi-guard/Cargo.toml | 2 + .../cpi-guard/tests/test_cpi_guard.rs | 6 +- .../cpi-guard/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/default-account-state/Cargo.toml | 2 + .../tests/test_default_account_state.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../group/anchor-v1/programs/group/Cargo.toml | 2 + .../programs/group/tests/test_group.rs | 9 +- .../group/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/immutable-owner/Cargo.toml | 2 + .../tests/test_immutable_owner.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/interest-bearing/Cargo.toml | 2 + .../tests/test_interest_bearing.rs | 9 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/memo-transfer/Cargo.toml | 2 + .../memo-transfer/tests/test_memo_transfer.rs | 6 +- .../memo-transfer/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/metadata/Cargo.toml | 2 + .../programs/metadata/tests/test_metadata.rs | 9 +- .../metadata/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/mint-close-authority/Cargo.toml | 2 + .../tests/test_mint_close_authority.rs | 9 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/extension_nft/Cargo.toml | 2 + .../extension_nft/tests/test_extension_nft.rs | 6 +- .../extension_nft/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/non-transferable/Cargo.toml | 2 + .../tests/test_non_transferable.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/permanent-delegate/Cargo.toml | 2 + .../tests/test_permanent_delegate.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-fee/Cargo.toml | 2 + .../transfer-fee/tests/test_transfer_fee.rs | 6 +- .../transfer-fee/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-hook/Cargo.toml | 2 + .../transfer-hook/tests/test_transfer_hook.rs | 6 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 +++++++ .../anchor-v1/programs/abl-token/Cargo.toml | 2 + .../abl-token/tests/test_abl_token.rs | 9 +- .../abl-token/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-hook/Cargo.toml | 2 + .../tests/test_transfer_hook_counter.rs | 6 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-hook/Cargo.toml | 2 + .../transfer-hook/tests/test_transfer_hook.rs | 6 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-hook/Cargo.toml | 2 + .../transfer-hook/tests/test_transfer_hook.rs | 6 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-switch/Cargo.toml | 2 + .../tests/test_transfer_switch.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-hook/Cargo.toml | 2 + .../transfer-hook/tests/test_transfer_hook.rs | 6 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/token-minter/Cargo.toml | 2 + .../token-minter/tests/test_token_minter.rs | 6 +- .../token-minter/tests/transaction_v1/mod.rs | 94 +++++++ .../programs/transfer-tokens/Cargo.toml | 2 + .../tests/test_transfer_tokens.rs | 6 +- .../tests/transaction_v1/mod.rs | 94 +++++++ 203 files changed, 5809 insertions(+), 1619 deletions(-) create mode 100644 basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs create mode 100644 basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs create mode 100644 basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs create mode 100644 basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs create mode 100644 basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs create mode 100644 basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs create mode 100644 basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs create mode 100644 basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs create mode 100644 basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs create mode 100644 basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs create mode 100644 basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs create mode 100644 basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs create mode 100644 basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs create mode 100644 basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs create mode 100644 basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs create mode 100644 basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs delete mode 100644 basics/transaction-v1/anchor/Anchor.toml delete mode 100644 basics/transaction-v1/anchor/CHANGELOG.md delete mode 100644 basics/transaction-v1/anchor/Cargo.toml delete mode 100644 basics/transaction-v1/anchor/README.md delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs delete mode 100644 basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs delete mode 100644 basics/transaction-v1/native/CHANGELOG.md delete mode 100644 basics/transaction-v1/native/Cargo.toml delete mode 100644 basics/transaction-v1/native/README.md delete mode 100644 basics/transaction-v1/native/program/Cargo.toml delete mode 100644 basics/transaction-v1/native/program/src/lib.rs delete mode 100644 basics/transaction-v1/native/program/tests/test_transaction_v1.rs delete mode 100644 basics/transaction-v1/pinocchio/CHANGELOG.md delete mode 100644 basics/transaction-v1/pinocchio/Cargo.toml delete mode 100644 basics/transaction-v1/pinocchio/README.md delete mode 100644 basics/transaction-v1/pinocchio/program/Cargo.toml delete mode 100644 basics/transaction-v1/pinocchio/program/src/lib.rs delete mode 100644 basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs create mode 100644 basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs create mode 100644 compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs create mode 100644 compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs create mode 100644 compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs delete mode 100644 docs/transaction-v1.md create mode 100644 finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs create mode 100644 finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs create mode 100644 finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs create mode 100644 finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs create mode 100644 finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs create mode 100644 finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs create mode 100644 finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs create mode 100644 finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs create mode 100644 finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs create mode 100644 finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs create mode 100644 tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs create mode 100644 tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs create mode 100644 tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs create mode 100644 tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs create mode 100644 tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs create mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs create mode 100644 tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs create mode 100644 tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a730d49..6ddf2b2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,48 @@ All notable changes to this repository are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2026-10-08] - Anchor v1 tests send v1 transactions + +Solana's v1 transaction format (SIMD-0385) activated on mainnet beta at epoch 1035 +(15 September 2026). It raises the transaction size limit from 1,232 to 4,096 bytes +and moves the compute budget out of ComputeBudget instructions and into the message. +Programs need no change for it; the client that builds the transaction does. + +### Changed + +- Every Anchor v1 example's tests send v1 transactions. Each test directory gains a + `transaction_v1` module (identical in all 57 examples) whose + `send_transaction_from_instructions` takes the same arguments and returns the same + error type as `solana-kite`'s, so each test file changes one import and no call + site. Kite's version builds a legacy transaction. The six tests that build a + transaction by hand (to read logs, measure compute units or return the metadata) + use the module's `v1_transaction` instead. +- A v1 transaction's unset limits are zero, not the legacy defaults, so the module + asks for exactly what a legacy transaction gets without ComputeBudget instructions: + 200,000 compute units per instruction up to 1,400,000, and 64 MiB of loaded + account data. The programs under test see the budget they always did. +- The test manifests name `solana-message` 4.2.4 and `solana-transaction` 4.1.5, + where the v1 types live. The three compression examples, which did not use kite, + add it for its error type, so the module is the same file everywhere. + +### Added + +- `basics/transaction-v1/anchor-v1`: a program that stores a 3,000 byte document in + one instruction, which only fits in a v1 transaction. Its tests measure the + transaction against both size limits, set each config field themselves, and show + that an unset field means zero rather than the default. + +### Note + +- Kite's token helpers (`create_token_mint`, `create_associated_token_account`, + `mint_tokens_to_token_account` and the token extension ones) send their setup + transactions through kite's own legacy builder, and still do. They switch when + kite's `send_transaction_from_instructions` builds v1; the module can then be + deleted and the imports pointed back at kite. +- Anchor v2 and Quasar tests still send legacy transactions: `anchor-v2-testing` + pins LiteSVM 0.13.1 and `quasar-svm` is on a `solana-message` without the `v1` + module. Both predate v1. + ## [2026-10-04] - Escrow: the taker signs the terms ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9568d257..520eafc01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,10 @@ Run an example's tests with the command for its framework, from the framework di - **Anchor v2** (in `anchor/`): `anchor test` (runs `cargo test`, per the `[scripts]` table in `Anchor.toml`), with the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.1 --locked`. - **Anchor v1** (in `anchor-v1/`): the same `anchor test`, with the v1 CLI: `avm install 1.2.0 && avm use 1.2.0`. Selecting the wrong CLI is the usual cause of a confusing build failure in these directories. + Anchor v1 tests send v1 transactions (4,096 bytes, resource limits in the message). Import + `send_transaction_from_instructions` from the example's `tests/transaction_v1` module rather than from + `solana-kite`, whose version builds a legacy transaction; the two take the same arguments. A new example + copies that module from any other `anchor-v1/` example unchanged. - **Quasar:** `quasar test`. - **Native / Pinocchio:** `cargo test --manifest-path=./program/Cargo.toml` (build first with `cargo build-sbf --manifest-path=./program/Cargo.toml`). diff --git a/Cargo.toml b/Cargo.toml index 37ef05820..e1b8135d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,6 @@ # declares its own `[workspace]`, so `anchor build` inside one resolves there. The # cost is that `cargo fmt` and `cargo clippy` never see them; the Anchor v1 workflow # compiles and tests them instead. -# -# `basics/transaction-v1/` is absent for a different reason. Its tests need LiteSVM -# 0.16, the first release that executes v1 transactions, while `anchor-v2-testing` -# and `solana-kite` pin 0.13.1, and the two cannot share this lockfile (each pins a -# different `solana-instruction` 3.x patch release). All four of its framework -# directories are standalone workspaces; the framework workflows build and test them. members = [ #basics "basics/account-data/native/program", diff --git a/README.md b/README.md index b6485e18b..5987cf87b 100644 --- a/README.md +++ b/README.md @@ -199,9 +199,9 @@ Send SOL between two accounts. ### Transaction v1 -Store a 3,000 byte document in one instruction using Solana's v1 transaction format, live on mainnet since September 2026: 4,096 bytes instead of 1,232, and a transaction config in the message in place of ComputeBudget instructions. The program needs no change for v1; the LiteSVM tests are what build the v1 transactions. See [docs/transaction-v1.md](./docs/transaction-v1.md) for what v1 changes and which tools support it. +Store a 3,000 byte document in one instruction using Solana's v1 transaction format, live on mainnet since September 2026: 4,096 bytes instead of 1,232, and a transaction config in the message in place of ComputeBudget instructions. The program needs no change for v1; the LiteSVM tests are what build the v1 transactions, and they show the config fields one by one. -[⚓ Anchor v2](./basics/transaction-v1/anchor) [⚓ Anchor v1](./basics/transaction-v1/anchor-v1) [🤥 Pinocchio](./basics/transaction-v1/pinocchio) [🦀 Native](./basics/transaction-v1/native) +[⚓ Anchor v1](./basics/transaction-v1/anchor-v1) ### Pyth Price Feeds @@ -421,7 +421,7 @@ Start with the [escrow example](./finance/escrow/anchor/), the best first financ ### Do these examples work with Solana transaction v1? -Yes. The v1 format (4,096 byte transactions, live on mainnet since epoch 1035 on 15 September 2026) changes how a client builds a transaction, not what a program sees, so every program here can be called through v1 as is. The [transaction v1 example](./basics/transaction-v1/anchor/) sends v1 transactions from its LiteSVM tests and shows the config fields that replace ComputeBudget instructions. The other examples' tests still send legacy transactions: `solana-kite` builds those, and the Anchor v2, native and Pinocchio tests are held on a pre-v1 LiteSVM by Anchor's `anchor-v2-testing`. [docs/transaction-v1.md](./docs/transaction-v1.md) tracks the status of each tool. +Yes. The v1 format (4,096 byte transactions, live on mainnet since epoch 1035 on 15 September 2026) changes how a client builds a transaction, not what a program sees, so every program here can be called through v1 as is. Every Anchor v1 example's tests send their transactions as v1, through a small `tests/transaction_v1` module, and the [transaction v1 example](./basics/transaction-v1/anchor-v1/) shows the config fields that replace ComputeBudget instructions. The Anchor v2 and Quasar tests still send legacy transactions, because their test harnesses run on virtual machines that predate v1. ### How do I test a Solana program without running a validator? diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml index 41b2eb10f..75b1400d3 100644 --- a/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs index 567a816a5..7124cc575 100644 --- a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs +++ b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -6,7 +10,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml index 6e89a52ef..1f3d91327 100644 --- a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs index 15cb44851..bef974c00 100644 --- a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs +++ b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_instruction, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/close-account/anchor-v1/programs/close-account/Cargo.toml b/basics/close-account/anchor-v1/programs/close-account/Cargo.toml index 803b58939..c01f6b1df 100644 --- a/basics/close-account/anchor-v1/programs/close-account/Cargo.toml +++ b/basics/close-account/anchor-v1/programs/close-account/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs b/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs index f7189df61..a2ded00d3 100644 --- a/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs +++ b/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs b/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml b/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml index ae0feab63..38ac2f222 100644 --- a/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml +++ b/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs b/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs index 97a3d3c03..d3a55fe98 100644 --- a/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs +++ b/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -6,7 +10,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs b/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml b/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml index 999f785bd..009186a50 100644 --- a/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml +++ b/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs b/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs index 70fe47ec5..ee04c17a5 100644 --- a/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs +++ b/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, rent::Rent, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs b/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml b/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml index d772bd85b..27ab0357c 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml +++ b/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs index e9c7c64ac..8cfb39c08 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs +++ b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -8,7 +12,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml b/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml index 6e93e57d1..8aacef29b 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml +++ b/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs index 47d32a638..6743a74e0 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs +++ b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/favorites/anchor-v1/programs/favorites/Cargo.toml b/basics/favorites/anchor-v1/programs/favorites/Cargo.toml index 764ffc2c5..6e317208f 100644 --- a/basics/favorites/anchor-v1/programs/favorites/Cargo.toml +++ b/basics/favorites/anchor-v1/programs/favorites/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs b/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs index 9f7a8b37d..7d13ca290 100644 --- a/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs +++ b/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs @@ -1,10 +1,14 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs b/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml b/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml index affdf2d25..a52156d70 100644 --- a/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml +++ b/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-kite = "0.5.0" solana-transaction = "4.1.5" +solana-keypair = "3.0.1" +solana-message = "4.2.4" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs index 6dd3541db..42e8accff 100644 --- a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs +++ b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs @@ -1,9 +1,12 @@ +mod transaction_v1; + +use transaction_v1::v1_transaction; + use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, solana_kite::create_wallet, solana_signer::Signer, - solana_transaction::Transaction, }; #[test] @@ -21,14 +24,9 @@ fn test_say_hello() { ); // The program only logs; assert it emitted its greeting rather than merely - // that the transaction landed. kite's send helper discards the metadata, so + // that the transaction landed. The send helper discards the metadata, so // send through LiteSVM directly to read the logs. - let transaction = Transaction::new_signed_with_payer( - &[instruction], - Some(&payer.pubkey()), - &[&payer], - svm.latest_blockhash(), - ); + let transaction = v1_transaction(&svm, &[instruction], &[&payer], &payer.pubkey()); let metadata = svm.send_transaction(transaction).unwrap(); assert!( diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml index 513e4b23f..81101db5b 100644 --- a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs index 650d71864..02fde57c2 100644 --- a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs +++ b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml b/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml index 16d562f5d..f401a8e91 100644 --- a/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml +++ b/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs index 413688cc4..3b08d0ef8 100644 --- a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs +++ b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs @@ -1,7 +1,11 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml index d44ae17cb..983e64a06 100644 --- a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs index 1ef8645bb..ea0cf0ce1 100644 --- a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs +++ b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, borsh::BorshDeserialize, litesvm::LiteSVM, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml b/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml index 2bde04bf9..1f7f46c82 100644 --- a/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml +++ b/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml @@ -37,6 +37,8 @@ solana-account = "4.3.0" borsh = "1.6.1" sha2 = "0.10" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs b/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs index d8c932a8c..168d4ea3a 100644 --- a/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs +++ b/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{clock::Clock, instruction::Instruction}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, pythexample::MAXIMUM_PRICE_AGE_SECONDS, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs b/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml b/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml index 765426c04..b367b3d72 100644 --- a/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml +++ b/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs index fecf1c6c1..249584db6 100644 --- a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs +++ b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -6,7 +10,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/rent/anchor-v1/programs/rent-example/Cargo.toml b/basics/rent/anchor-v1/programs/rent-example/Cargo.toml index 7b3de4c73..d0c8773c2 100644 --- a/basics/rent/anchor-v1/programs/rent-example/Cargo.toml +++ b/basics/rent/anchor-v1/programs/rent-example/Cargo.toml @@ -28,6 +28,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs b/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs index 2451d0dcb..f68fd3d24 100644 --- a/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs +++ b/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -6,7 +10,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs b/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml b/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml index 82de27436..8f18f47c3 100644 --- a/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml +++ b/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs b/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs index c59114e23..6d382f47d 100644 --- a/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs +++ b/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs @@ -1,7 +1,11 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs b/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/basics/transaction-v1/anchor-v1/CHANGELOG.md b/basics/transaction-v1/anchor-v1/CHANGELOG.md index 41a058f79..72786bcc3 100644 --- a/basics/transaction-v1/anchor-v1/CHANGELOG.md +++ b/basics/transaction-v1/anchor-v1/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026-09-22 +## 2026-10-08 Added: a program that stores a 3,000 byte document in one instruction, tested with LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/anchor-v1/README.md b/basics/transaction-v1/anchor-v1/README.md index 75404c802..af92b21df 100644 --- a/basics/transaction-v1/anchor-v1/README.md +++ b/basics/transaction-v1/anchor-v1/README.md @@ -1,14 +1,13 @@ # Solana Transaction v1 (Anchor) > [!NOTE] -> This is the **Anchor v1** copy of this example, kept for programs staying on the -> Anchor v1 LTS line. Every `anchor` command on this page needs the v1 CLI: -> `avm install 1.2.0 && avm use 1.2.0`. The Anchor v2 version of this example is in -> [`../anchor`](../anchor/). +> This example is on Anchor 1.2.0, the current stable Anchor release. Every `anchor` +> command on this page needs the v1 CLI: `avm install 1.2.0 && avm use 1.2.0`. There is +> no Anchor v2 copy yet: Anchor v2's test harness runs on a LiteSVM that predates v1. A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). Nothing in the program is specific to v1: an Anchor program reads its accounts and instruction data the same way whichever format carried them, and no Anchor change is needed to be called through v1. The tests are where v1 shows up, because the client builds the transaction. -See also the [Anchor v2](../anchor/), [Pinocchio](../pinocchio/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. +See also: the [repository catalog](../../../README.md). ## Major concepts @@ -42,7 +41,7 @@ Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/lit anchor test ``` -This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example builds its transactions by hand rather than with `solana-kite`, which the other Anchor v1 examples use: kite's `send_transaction_from_instructions` builds a legacy transaction. The suite covers: +This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example builds its transactions by hand. Every other Anchor v1 example sends v1 transactions too, through its `tests/transaction_v1` module, which asks for the same limits a legacy transaction gets by default; this one sets each config field itself to show what they do. The suite covers: - `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. - `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. @@ -53,4 +52,4 @@ LiteSVM does not enforce either size limit (a validator's packet layer does), wh ## Sending v1 transactions to a real cluster -The v1 format is live on mainnet beta since epoch 1035 (15 September 2026), and on devnet and testnet, behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). Sending one needs Agave 4.2 or later client crates and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. Anchor's TypeScript client (`@coral-xyz/anchor`) builds its transactions with `@solana/web3.js` 1.x, which can read v1 transactions but not build them, so a v1 transaction to an Anchor program is built with `@solana/kit` 8 or later, or in Rust as in the tests here. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the details and the client libraries that can build v1 transactions. +The v1 format is live on mainnet beta since epoch 1035 (15 September 2026), and on devnet and testnet, behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). Sending one needs Agave 4.2 or later client crates and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. Anchor's TypeScript client (`@coral-xyz/anchor`) builds its transactions with `@solana/web3.js` 1.x, which can read v1 transactions but not build them, so a v1 transaction to an Anchor program is built with `@solana/kit` 8 or later, or in Rust as in the tests here. diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml index 3307bbe4c..c1f60467c 100644 --- a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml @@ -28,9 +28,9 @@ anchor-lang = "1.2.0" # size from the message's transaction config rather than from ComputeBudget # instructions. # -# The other Anchor v1 examples build their transactions with `solana-kite`, -# whose `send_transaction_from_instructions` builds a legacy transaction. -# This example builds its v1 transactions by hand instead. +# The other Anchor v1 examples send v1 transactions through their +# `tests/transaction_v1` module with legacy-equivalent limits. This example +# builds its own, to show each config field. litesvm = "0.16.0" borsh = "1.6.1" solana-keypair = "3.1" diff --git a/basics/transaction-v1/anchor/Anchor.toml b/basics/transaction-v1/anchor/Anchor.toml deleted file mode 100644 index 8584e8f71..000000000 --- a/basics/transaction-v1/anchor/Anchor.toml +++ /dev/null @@ -1,18 +0,0 @@ -[toolchain] -# Match the repo package manager (pnpm-lock.yaml at root); avoids Anchor's yarn default. -package_manager = "pnpm" -solana_version = "3.1.8" - -[features] -seeds = false -skip-lint = false - -[programs.localnet] -transaction_v1_anchor_program = "F1dMqenFVwcp3SmX9Sq9e22nmcxcact5No12pmb5rL5F" - -[provider] -cluster = "Localnet" -wallet = "~/.config/solana/id.json" - -[scripts] -test = "cargo test" diff --git a/basics/transaction-v1/anchor/CHANGELOG.md b/basics/transaction-v1/anchor/CHANGELOG.md deleted file mode 100644 index 41a058f79..000000000 --- a/basics/transaction-v1/anchor/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# Changelog - -## 2026-09-22 - -Added: a program that stores a 3,000 byte document in one instruction, tested with -LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/anchor/Cargo.toml b/basics/transaction-v1/anchor/Cargo.toml deleted file mode 100644 index 16145313a..000000000 --- a/basics/transaction-v1/anchor/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -# This workspace is deliberately absent from the repository root `Cargo.toml` -# members list, unlike every other `anchor/` example. -# -# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and -# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first -# release that executes v1 transactions, and the two cannot share a lockfile: -# each pins a different `solana-instruction` 3.x patch release. Until the root -# workspace moves to LiteSVM 0.16+, this example resolves on its own, and the -# Anchor v2 workflow (not the root `cargo clippy`) is what keeps it honest. -[workspace] -members = [ - "programs/*" -] -resolver = "2" - -[profile.release] -overflow-checks = true -lto = "fat" -codegen-units = 1 -[profile.release.build-override] -opt-level = 3 -incremental = false -codegen-units = 1 diff --git a/basics/transaction-v1/anchor/README.md b/basics/transaction-v1/anchor/README.md deleted file mode 100644 index 02eb76843..000000000 --- a/basics/transaction-v1/anchor/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Solana Transaction v1 (Anchor) - -> [!NOTE] -> This is the **Anchor v2** copy of this example. Every `anchor` command on this page -> needs the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.1 --locked` (avm has -> no prebuilt binary for this pre-release). The Anchor v1 version of this example is in -> [`../anchor-v1`](../anchor-v1/). - -A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). Nothing in the program is specific to v1: an Anchor program reads its accounts and instruction data the same way whichever format carried them, and no Anchor change is needed to be called through v1. The tests are where v1 shows up, because the client builds the transaction. - -See also the [Anchor v1](../anchor-v1/), [Pinocchio](../pinocchio/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. - -## Major concepts - -**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. - -**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as the `document: Vec` argument exactly as a 300 byte one would. - -**The document account.** `Document`, a [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer. It is a `BorshAccount` because its `data: Vec` has whatever length the caller chose, which v2's default zero-copy account backing cannot hold. There is no `InitSpace` either: `Document::required_space` sizes the account to each document (discriminator, borsh length prefix, bytes), and the payer pays that [rent](https://solana.com/docs/references/terminology#rent). - -## Lifecycle - -A payer signs a transaction carrying one `store_document` instruction with the document as its argument. The `StoreDocumentAccountConstraints` `init` constraint creates the PDA sized to the document and the handler rejects an empty document (`DocumentError::EmptyDocument`) before copying it in. - -## Setup - -From this directory (`basics/transaction-v1/anchor/`): - -```bash -anchor build -``` - -Prerequisites: [Agave](https://docs.anza.xyz/) CLI (version in `Anchor.toml` `[toolchain]`; the LiteSVM tests carry their own v1-capable runtime, so the CLI version does not matter to them), [Anchor](https://www.anchor-lang.com/docs) v2. - -This example is its own Cargo workspace and is deliberately absent from the repository root workspace's members: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. - -## Testing - -Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm). No local validator. - -```bash -anchor test -``` - -This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example calls it directly rather than through `anchor-v2-testing`, the harness the other Anchor v2 examples use: that harness pins LiteSVM 0.13.1, so `anchor test --profile`, `anchor debugger` and `anchor coverage` see nothing for this example until it moves. The suite covers: - -- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. -- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. -- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. -- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). - -LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. - -## Sending v1 transactions to a real cluster - -The v1 format is live on mainnet beta since epoch 1035 (15 September 2026), and on devnet and testnet, behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). Sending one needs Agave 4.2 or later client crates and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. Anchor's TypeScript client (`@coral-xyz/anchor`) builds its transactions with `@solana/web3.js` 1.x, which can read v1 transactions but not build them, so a v1 transaction to an Anchor program is built with `@solana/kit` 8 or later, or in Rust as in the tests here. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the details and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml b/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml deleted file mode 100644 index f2df55f6c..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "transaction-v1-anchor-program" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "transaction_v1_anchor_program" - -[features] -default = [] -cpi = ["no-entrypoint"] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build"] -anchor-debug = [] -custom-heap = [] -custom-panic = [] - -[dependencies] -anchor-lang = "2.0.0-rc.1" -# The `#[program]` macro expands to `wincode` paths for instruction-data -# (de)serialization, so the crate has to be a direct dependency. -wincode = { version = "0.5", features = ["derive"] } -# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 -# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls -# belong to the version the account derives are not using and every -# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. -solana-address = ">=2.6, <2.7" - -[dev-dependencies] -# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the -# compute unit limit, priority fee, loaded-accounts data size limit and heap -# size from the message's transaction config rather than from ComputeBudget -# instructions. -# -# The other Anchor v2 examples get their LiteSVM through `anchor-v2-testing`, -# whose `svm()` also records the traces behind `anchor test --profile`, -# `anchor debugger` and `anchor coverage`. That harness pins LiteSVM to -# exactly 0.13.1, which cannot execute a v1 transaction, so this example uses -# LiteSVM directly and those three tools see nothing for it. Switch back to -# the harness once it moves to LiteSVM 0.16 or later. -litesvm = "0.16.0" -borsh = "1.6.1" -solana-keypair = "3.1" -# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 -# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. -solana-message = "4.2" -solana-native-token = "3.0" -# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. -solana-packet = "4.2" -solana-transaction = "4.1" -solana-transaction-error = "3.3" - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml b/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml deleted file mode 100644 index 475fb71ed..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs deleted file mode 100644 index 89cbfd5e6..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod store; - -pub use store::*; diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs deleted file mode 100644 index 337709a4b..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/src/instructions/store.rs +++ /dev/null @@ -1,37 +0,0 @@ -// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more -// than one generated item, and only the one evaluating the constraints below -// reads them, so the binding looks unused to rustc even though `space` uses it. -#![allow(unused_variables)] - -use crate::{state::Document, DocumentError}; -use anchor_lang::prelude::*; - -#[derive(Accounts)] -#[instruction(document: Vec)] -pub struct StoreDocumentAccountConstraints { - #[account(mut)] - pub payer: Signer, - - /// Sized to this document exactly, so the rent matches what was stored. - #[account( - init, - payer = payer, - space = Document::required_space(document.len()), - seeds = [ - Document::SEED_PREFIX, - payer.address().as_ref(), - ], - bump, - )] - pub document_account: BorshAccount, - pub system_program: Program, -} - -pub fn handle_store_document( - context: &mut Context, - document: Vec, -) -> Result<()> { - require!(!document.is_empty(), DocumentError::EmptyDocument); - context.accounts.document_account.data = document; - Ok(()) -} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs deleted file mode 100644 index 534d8b3fb..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Store a document of arbitrary size in a program-owned account, in one -//! instruction. -//! -//! The program itself has nothing to do with transaction v1: it reads the -//! accounts and instruction data it is given like any other program, and does -//! not know or care which transaction format carried them. What v1 changes is -//! how much a single instruction can carry: a 3,000 byte document is beyond -//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it -//! in a v1 transaction (up to 4,096 bytes) instead. - -use anchor_lang::prelude::*; -use instructions::*; - -pub mod instructions; -pub mod state; - -declare_id!("F1dMqenFVwcp3SmX9Sq9e22nmcxcact5No12pmb5rL5F"); - -#[program] -pub mod transaction_v1_anchor_program { - use super::*; - - pub fn store_document( - context: &mut Context, - document: Vec, - ) -> Result<()> { - store::handle_store_document(context, document) - } -} - -#[error_code] -pub enum DocumentError { - #[msg("A document cannot be empty")] - EmptyDocument, -} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs deleted file mode 100644 index 2e22c50fb..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/document.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anchor_lang::prelude::*; - -// `borsh` because the struct holds a `Vec` whose length the caller -// chooses: v2's default `#[account]` backing is zero-copy and needs a `Pod` -// (fixed-layout) type. There is deliberately no `InitSpace` either: the -// account is sized to each document rather than to a compile-time maximum. -#[account(borsh)] -pub struct Document { - pub data: Vec, -} - -impl Document { - pub const SEED_PREFIX: &'static [u8; 8] = b"document"; - - /// Discriminator, then the borsh length prefix, then the bytes. - pub fn required_space(document_len: usize) -> usize { - Self::DISCRIMINATOR.len() + 4 + document_len - } -} diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs b/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs deleted file mode 100644 index d0889e2ad..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/src/state/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod document; - -pub use document::*; diff --git a/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs b/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs deleted file mode 100644 index 61299e4ef..000000000 --- a/basics/transaction-v1/anchor/programs/transaction-v1/tests/test_transaction_v1.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! The program is exercised with both transaction formats: legacy for a small -//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also -//! cover the part of the format that is easy to get wrong: the transaction -//! config that replaces ComputeBudget instructions, whose unset fields mean -//! zero rather than "use the default". - -use { - anchor_lang::{ - solana_program::instruction::Instruction, system_program, Address, InstructionData, - ToAccountMetas, - }, - borsh::BorshDeserialize, - litesvm::LiteSVM, - solana_keypair::{Keypair, Signer}, - solana_message::{v1, VersionedMessage}, - solana_native_token::LAMPORTS_PER_SOL, - solana_packet::PACKET_DATA_SIZE, - solana_transaction::{versioned::VersionedTransaction, Transaction}, - solana_transaction_error::TransactionError, - transaction_v1_anchor_program::state::Document, -}; - -/// What LiteSVM charges per signature, and what mainnet has charged since -/// genesis. -const BASE_FEE_LAMPORTS: u64 = 5_000; - -/// Enough for the account creation and the copy. Unlike a legacy transaction, -/// a v1 transaction has no 200,000 CU default to fall back on: leaving the -/// limit unset means zero. -const COMPUTE_UNIT_LIMIT: u32 = 50_000; - -/// The payer, the new account, the system program and the program itself -/// together load well under 64 KiB. Like the compute unit limit, this defaults -/// to zero when unset. -const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; - -/// A v1 transaction's config carries the resource limits that legacy and v0 -/// transactions express with ComputeBudget program instructions. Every test -/// that expects to succeed starts from this one. -fn transaction_config() -> v1::TransactionConfig { - v1::TransactionConfig::empty() - .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) -} - -/// The account as Anchor lays it out: discriminator, then the borsh `Vec`. -#[derive(BorshDeserialize)] -struct DocumentAccount { - _discriminator: [u8; 8], - data: Vec, -} - -fn setup() -> (LiteSVM, Address, Keypair) { - let program_id = transaction_v1_anchor_program::id(); - let mut svm = LiteSVM::new(); - let program_bytes = include_bytes!("../../../target/deploy/transaction_v1_anchor_program.so"); - svm.add_program(program_id, program_bytes).unwrap(); - - let payer = Keypair::new(); - svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); - (svm, program_id, payer) -} - -/// The instruction and the PDA it will create. -fn store_document( - program_id: &Address, - payer: &Address, - document: &[u8], -) -> (Instruction, Address) { - let (document_address, _bump) = - Address::find_program_address(&[Document::SEED_PREFIX, payer.as_ref()], program_id); - let instruction = Instruction::new_with_bytes( - *program_id, - &transaction_v1_anchor_program::instruction::StoreDocument { - document: document.to_vec(), - } - .data(), - transaction_v1_anchor_program::accounts::StoreDocumentAccountConstraints { - payer: *payer, - document_account: document_address, - system_program: system_program::ID, - } - .to_account_metas(None), - ); - (instruction, document_address) -} - -/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 -/// counterpart of `Message::new_with_blockhash`; the config rides in the -/// message header, so there is no ComputeBudget instruction to append. -fn v1_transaction( - svm: &LiteSVM, - payer: &Keypair, - instructions: &[Instruction], - config: v1::TransactionConfig, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - &payer.pubkey(), - instructions, - svm.latest_blockhash(), - config, - ) - .unwrap(); - VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() -} - -/// The bytes that would be sent to an RPC node. -fn wire_size(transaction: &VersionedTransaction) -> usize { - wincode::serialize(transaction).unwrap().len() -} - -/// A document with a recognizable pattern, so a partial or shifted copy -/// would not pass the equality check. -fn sample_document(length: usize) -> Vec { - (0..length).map(|i| (i % 251) as u8).collect() -} - -fn stored_document(svm: &LiteSVM, document_address: &Address) -> Vec { - let account = svm.get_account(document_address).unwrap(); - DocumentAccount::try_from_slice(&account.data).unwrap().data -} - -#[test] -fn stores_a_document_too_large_for_a_legacy_transaction() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); - - // Over the legacy limit, under the v1 one. LiteSVM does not enforce either - // (a validator's packet layer does), so the test checks the size itself. - let size = wire_size(&transaction); - assert!( - size > PACKET_DATA_SIZE, - "{size} bytes fits a legacy transaction" - ); - assert!( - size <= v1::MAX_TRANSACTION_SIZE, - "{size} bytes is over the v1 limit" - ); - - svm.send_transaction(transaction).unwrap(); - - let account = svm.get_account(&document_address).unwrap(); - assert_eq!(account.owner, program_id); - assert_eq!(account.data.len(), Document::required_space(document.len())); - assert_eq!(stored_document(&svm, &document_address), document); -} - -#[test] -fn a_legacy_transaction_still_works_for_a_small_document() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - // Nothing about the program changed: the same instruction goes through the - // legacy format when it fits. - let transaction = Transaction::new_signed_with_payer( - &[instruction], - Some(&payer.pubkey()), - &[&payer], - svm.latest_blockhash(), - ); - assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); - - svm.send_transaction(transaction).unwrap(); - - assert_eq!(stored_document(&svm, &document_address), document); -} - -#[test] -fn the_transaction_config_replaces_compute_budget_instructions() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - let rent = svm.minimum_balance_for_rent_exemption(Document::required_space(document.len())); - - // A v1 priority fee is a total in lamports, not a price per compute unit. - let priority_fee_lamports = 10_000; - let config = transaction_config().with_priority_fee(priority_fee_lamports); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - - // v1 moved the signatures to the end, so the first byte on the wire is the - // message version rather than a signature count. 0x81 means v1. - assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); - - let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); - let metadata = svm.send_transaction(transaction).unwrap(); - let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); - - assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); - assert_eq!( - balance_before - balance_after, - rent + BASE_FEE_LAMPORTS + priority_fee_lamports - ); -} - -#[test] -fn unset_config_fields_mean_zero_not_the_default() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - - // No loaded-accounts data size limit: the transaction fails before any - // program runs, because zero bytes of account data may be loaded. - let transaction = v1_transaction( - &svm, - &payer, - std::slice::from_ref(&instruction), - v1::TransactionConfig::empty(), - ); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert_eq!( - failure.err, - TransactionError::MaxLoadedAccountsDataSizeExceeded - ); - - // Data size limit set, compute unit limit not: the program starts with a - // budget of zero and fails on its first instruction. - let config = v1::TransactionConfig::empty() - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert!(matches!( - failure.err, - TransactionError::InstructionError(0, _) - )); - assert!( - failure - .meta - .logs - .iter() - .any(|log| log.contains("exceeded CUs")), - "{:?}", - failure.meta.logs - ); -} diff --git a/basics/transaction-v1/native/CHANGELOG.md b/basics/transaction-v1/native/CHANGELOG.md deleted file mode 100644 index 41a058f79..000000000 --- a/basics/transaction-v1/native/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# Changelog - -## 2026-09-22 - -Added: a program that stores a 3,000 byte document in one instruction, tested with -LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/native/Cargo.toml b/basics/transaction-v1/native/Cargo.toml deleted file mode 100644 index ba4d78ae2..000000000 --- a/basics/transaction-v1/native/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Standalone workspace, deliberately outside the repository root workspace. -# -# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and -# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first -# release that executes v1 transactions, and the two cannot share a lockfile: -# each pins a different `solana-instruction` 3.x patch release. Until the root -# workspace moves to LiteSVM 0.16+, this example resolves on its own. -[workspace] -members = ["program"] -resolver = "2" - -[profile.release] -overflow-checks = true diff --git a/basics/transaction-v1/native/README.md b/basics/transaction-v1/native/README.md deleted file mode 100644 index b669d2556..000000000 --- a/basics/transaction-v1/native/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Solana Transaction v1 (Native) - -A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). The program is written directly against `solana-program`, with no framework. Nothing in the program is specific to v1: it reads its accounts and instruction data the same way whichever format carried them. The tests are where v1 shows up, because the client builds the transaction. - -See also the [Anchor v2](../anchor/), [Anchor v1](../anchor-v1/) and [Pinocchio](../pinocchio/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. - -## Major concepts - -**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. - -**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as instruction data exactly as a 300 byte one would. - -**The document account.** A [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer, created by the program with a size equal to the document and owned by the program. The payer pays the [rent](https://solana.com/docs/references/terminology#rent). - -## Lifecycle - -A payer signs a transaction carrying one instruction: the `STORE_DOCUMENT` discriminator byte followed by the document. The `store_document` handler checks that the payer signed, that the document is not empty, that the third account is the System Program, and that the second account is the payer's document PDA. It then creates the PDA through a signed CPI to the System Program, sized to the document, and copies the document in. - -## Setup - -Prerequisites: the [Agave](https://docs.anza.xyz/) toolchain (`cargo build-sbf`) and Rust. - -From this directory (`basics/transaction-v1/native/`): - -```bash -cargo build-sbf --manifest-path=./program/Cargo.toml -``` - -This writes the program binary to `target/deploy/transaction_v1_native_program.so`. - -This example is its own Cargo workspace rather than a member of the repository root workspace: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. - -## Testing - -The Rust + [LiteSVM](https://www.litesvm.com/) tests load the `.so` built above. The binary is embedded at test-compile time, so rebuild after every program change or a stale `.so` silently tests old code. After building, run: - -```bash -cargo test --manifest-path=./program/Cargo.toml -``` - -LiteSVM 0.16.0 is the first release that executes v1 transactions. The suite covers: - -- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. -- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. -- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. -- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). - -LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. - -## Sending v1 transactions to a real cluster - -The v1 format is live on mainnet beta since epoch 1035 (15 September 2026), and on devnet and testnet, behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). Sending one needs Agave 4.2 or later client crates and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the details and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/native/program/Cargo.toml b/basics/transaction-v1/native/program/Cargo.toml deleted file mode 100644 index 04c066c46..000000000 --- a/basics/transaction-v1/native/program/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "transaction-v1-native-program" -version = "0.1.0" -edition = "2021" - -[dependencies] -solana-program = "4.0" -solana-system-interface = { version = "2.0.0", features = ["bincode"] } - -[lib] -crate-type = ["cdylib", "lib"] - -[features] -custom-heap = [] -custom-panic = [] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } - -[dev-dependencies] -# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the -# compute unit limit, priority fee, loaded-accounts data size limit and heap -# size from the message's transaction config rather than from ComputeBudget -# instructions. -litesvm = "0.16.0" -solana-instruction = "3.4" -solana-keypair = "3.1" -# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 -# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. -solana-message = "4.2" -solana-native-token = "3.0" -# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. -solana-packet = "4.2" -solana-pubkey = "3.0" -solana-transaction = "4.1" -solana-transaction-error = "3.3" -# The SDK's serializer. `wincode::serialize` on a `VersionedTransaction` gives -# the exact bytes that would go over the wire, which is how the tests measure -# transaction size. -wincode = "0.5" diff --git a/basics/transaction-v1/native/program/src/lib.rs b/basics/transaction-v1/native/program/src/lib.rs deleted file mode 100644 index 128fdfd7b..000000000 --- a/basics/transaction-v1/native/program/src/lib.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Store a document of arbitrary size in a program-owned account, in one -//! instruction. -//! -//! The program itself has nothing to do with transaction v1: it reads the -//! accounts and instruction data it is given like any other program, and does -//! not know or care which transaction format carried them. What v1 changes is -//! how much a single instruction can carry: a 3,000 byte document is beyond -//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it -//! in a v1 transaction (up to 4,096 bytes) instead. - -use solana_program::{ - account_info::{next_account_info, AccountInfo}, - entrypoint, - entrypoint::ProgramResult, - program::invoke_signed, - program_error::ProgramError, - pubkey::Pubkey, - rent::Rent, - sysvar::Sysvar, -}; - -/// First byte of the instruction data. Everything after it is the document. -pub const STORE_DOCUMENT: u8 = 0; - -/// Seed prefix of the document account. One document per payer. -pub const DOCUMENT_SEED: &[u8] = b"document"; - -entrypoint!(process_instruction); - -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - match instruction_data.split_first() { - Some((&STORE_DOCUMENT, document)) => store_document(program_id, accounts, document), - _ => Err(ProgramError::InvalidInstructionData), - } -} - -/// Create the payer's document PDA, sized to the document exactly, and copy -/// the document into it. Rent comes from the payer. -fn store_document(program_id: &Pubkey, accounts: &[AccountInfo], document: &[u8]) -> ProgramResult { - let accounts_iter = &mut accounts.iter(); - let payer = next_account_info(accounts_iter)?; - let document_account = next_account_info(accounts_iter)?; - let system_program = next_account_info(accounts_iter)?; - - if !payer.is_signer { - return Err(ProgramError::MissingRequiredSignature); - } - if document.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - if !solana_system_interface::program::check_id(system_program.key) { - return Err(ProgramError::IncorrectProgramId); - } - - // The client passes the PDA; derive it here so nobody can hand us some - // other account to write into. - let (expected_address, bump) = - Pubkey::find_program_address(&[DOCUMENT_SEED, payer.key.as_ref()], program_id); - if *document_account.key != expected_address { - return Err(ProgramError::InvalidSeeds); - } - - let lamports_required = Rent::get()?.minimum_balance(document.len()); - - invoke_signed( - &solana_system_interface::instruction::create_account( - payer.key, - document_account.key, - lamports_required, - document.len() as u64, - program_id, - ), - &[ - payer.clone(), - document_account.clone(), - system_program.clone(), - ], - &[&[DOCUMENT_SEED, payer.key.as_ref(), &[bump]]], - )?; - - document_account.data.borrow_mut().copy_from_slice(document); - - Ok(()) -} diff --git a/basics/transaction-v1/native/program/tests/test_transaction_v1.rs b/basics/transaction-v1/native/program/tests/test_transaction_v1.rs deleted file mode 100644 index 41010f8cd..000000000 --- a/basics/transaction-v1/native/program/tests/test_transaction_v1.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! The program is exercised with both transaction formats: legacy for a small -//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also -//! cover the part of the format that is easy to get wrong: the transaction -//! config that replaces ComputeBudget instructions, whose unset fields mean -//! zero rather than "use the default". - -use { - litesvm::LiteSVM, - solana_instruction::{AccountMeta, Instruction}, - solana_keypair::{Keypair, Signer}, - solana_message::{v1, VersionedMessage}, - solana_native_token::LAMPORTS_PER_SOL, - solana_packet::PACKET_DATA_SIZE, - solana_pubkey::Pubkey, - solana_transaction::{versioned::VersionedTransaction, Transaction}, - solana_transaction_error::TransactionError, - transaction_v1_native_program::{DOCUMENT_SEED, STORE_DOCUMENT}, -}; - -/// What LiteSVM charges per signature, and what mainnet has charged since -/// genesis. -const BASE_FEE_LAMPORTS: u64 = 5_000; - -/// Enough for `create_account` plus the copy. Unlike a legacy transaction, a -/// v1 transaction has no 200,000 CU default to fall back on: leaving the -/// limit unset means zero. -const COMPUTE_UNIT_LIMIT: u32 = 20_000; - -/// The payer, the new account, the system program and the program itself -/// together load well under 64 KiB. Like the compute unit limit, this defaults -/// to zero when unset. -const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; - -/// A v1 transaction's config carries the resource limits that legacy and v0 -/// transactions express with ComputeBudget program instructions. Every test -/// that expects to succeed starts from this one. -fn transaction_config() -> v1::TransactionConfig { - v1::TransactionConfig::empty() - .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) -} - -fn setup() -> (LiteSVM, Pubkey, Keypair) { - let mut svm = LiteSVM::new(); - let program_id = Pubkey::new_unique(); - // The .so is built into the workspace target/deploy by - // `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the - // project root). Rebuild after every program change: the binary is - // embedded at test-compile time, so a stale .so silently tests old code. - let program_bytes = include_bytes!("../../target/deploy/transaction_v1_native_program.so"); - svm.add_program(program_id, program_bytes).unwrap(); - - let payer = Keypair::new(); - svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); - (svm, program_id, payer) -} - -/// The instruction and the PDA it will create. -fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Instruction, Pubkey) { - let (document_address, _bump) = - Pubkey::find_program_address(&[DOCUMENT_SEED, payer.as_ref()], program_id); - let mut data = Vec::with_capacity(1 + document.len()); - data.push(STORE_DOCUMENT); - data.extend_from_slice(document); - let instruction = Instruction { - program_id: *program_id, - accounts: vec![ - AccountMeta::new(*payer, true), - AccountMeta::new(document_address, false), - AccountMeta::new_readonly(solana_system_interface::program::ID, false), - ], - data, - }; - (instruction, document_address) -} - -/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 -/// counterpart of `Message::new_with_blockhash`; the config rides in the -/// message header, so there is no ComputeBudget instruction to append. -fn v1_transaction( - svm: &LiteSVM, - payer: &Keypair, - instructions: &[Instruction], - config: v1::TransactionConfig, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - &payer.pubkey(), - instructions, - svm.latest_blockhash(), - config, - ) - .unwrap(); - VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() -} - -/// The bytes that would be sent to an RPC node. -fn wire_size(transaction: &VersionedTransaction) -> usize { - wincode::serialize(transaction).unwrap().len() -} - -/// A document with a recognizable pattern, so a partial or shifted copy -/// would not pass the equality check. -fn sample_document(length: usize) -> Vec { - (0..length).map(|i| (i % 251) as u8).collect() -} - -#[test] -fn stores_a_document_too_large_for_a_legacy_transaction() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); - - // Over the legacy limit, under the v1 one. LiteSVM does not enforce either - // (a validator's packet layer does), so the test checks the size itself. - let size = wire_size(&transaction); - assert!( - size > PACKET_DATA_SIZE, - "{size} bytes fits a legacy transaction" - ); - assert!( - size <= v1::MAX_TRANSACTION_SIZE, - "{size} bytes is over the v1 limit" - ); - - svm.send_transaction(transaction).unwrap(); - - let account = svm.get_account(&document_address).unwrap(); - assert_eq!(account.owner, program_id); - assert_eq!(account.data, document); -} - -#[test] -fn a_legacy_transaction_still_works_for_a_small_document() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - // Nothing about the program changed: the same instruction goes through the - // legacy format when it fits. - let transaction = Transaction::new_signed_with_payer( - &[instruction], - Some(&payer.pubkey()), - &[&payer], - svm.latest_blockhash(), - ); - assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); - - svm.send_transaction(transaction).unwrap(); - - assert_eq!(svm.get_account(&document_address).unwrap().data, document); -} - -#[test] -fn the_transaction_config_replaces_compute_budget_instructions() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - let rent = svm.minimum_balance_for_rent_exemption(document.len()); - - // A v1 priority fee is a total in lamports, not a price per compute unit. - let priority_fee_lamports = 10_000; - let config = transaction_config().with_priority_fee(priority_fee_lamports); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - - // v1 moved the signatures to the end, so the first byte on the wire is the - // message version rather than a signature count. 0x81 means v1. - assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); - - let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); - let metadata = svm.send_transaction(transaction).unwrap(); - let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); - - assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); - assert_eq!( - balance_before - balance_after, - rent + BASE_FEE_LAMPORTS + priority_fee_lamports - ); -} - -#[test] -fn unset_config_fields_mean_zero_not_the_default() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - - // No loaded-accounts data size limit: the transaction fails before any - // program runs, because zero bytes of account data may be loaded. - let transaction = v1_transaction( - &svm, - &payer, - std::slice::from_ref(&instruction), - v1::TransactionConfig::empty(), - ); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert_eq!( - failure.err, - TransactionError::MaxLoadedAccountsDataSizeExceeded - ); - - // Data size limit set, compute unit limit not: the program starts with a - // budget of zero and fails on its first instruction. - let config = v1::TransactionConfig::empty() - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert!(matches!( - failure.err, - TransactionError::InstructionError(0, _) - )); - assert!( - failure - .meta - .logs - .iter() - .any(|log| log.contains("exceeded CUs")), - "{:?}", - failure.meta.logs - ); -} diff --git a/basics/transaction-v1/pinocchio/CHANGELOG.md b/basics/transaction-v1/pinocchio/CHANGELOG.md deleted file mode 100644 index 41a058f79..000000000 --- a/basics/transaction-v1/pinocchio/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# Changelog - -## 2026-09-22 - -Added: a program that stores a 3,000 byte document in one instruction, tested with -LiteSVM 0.16 by sending the instruction in a transaction v1 (4,096 byte) transaction. diff --git a/basics/transaction-v1/pinocchio/Cargo.toml b/basics/transaction-v1/pinocchio/Cargo.toml deleted file mode 100644 index ba4d78ae2..000000000 --- a/basics/transaction-v1/pinocchio/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Standalone workspace, deliberately outside the repository root workspace. -# -# The root workspace pins LiteSVM 0.13.1 through `anchor-v2-testing` and -# `solana-kite`, and 0.13.1 predates transaction v1. LiteSVM 0.16.0 is the first -# release that executes v1 transactions, and the two cannot share a lockfile: -# each pins a different `solana-instruction` 3.x patch release. Until the root -# workspace moves to LiteSVM 0.16+, this example resolves on its own. -[workspace] -members = ["program"] -resolver = "2" - -[profile.release] -overflow-checks = true diff --git a/basics/transaction-v1/pinocchio/README.md b/basics/transaction-v1/pinocchio/README.md deleted file mode 100644 index 013e9cdec..000000000 --- a/basics/transaction-v1/pinocchio/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Solana Transaction v1 (Pinocchio) - -A Solana transaction v1 example: a program that stores a 3,000 byte document in a single [instruction](https://solana.com/docs/references/terminology#instruction), which only fits inside the v1 [transaction](https://solana.com/docs/references/terminology#transaction) format (4,096 bytes, up from the 1,232 bytes of legacy and v0 transactions). The program is written with [Pinocchio](https://github.com/anza-xyz/pinocchio), the zero-copy, zero-allocation program library. Nothing in the program is specific to v1: it reads its accounts and instruction data the same way whichever format carried them. The tests are where v1 shows up, because the client builds the transaction. - -See also the [Anchor v2](../anchor/), [Anchor v1](../anchor-v1/) and [native Rust](../native/) variants of the same program, and [docs/transaction-v1.md](../../../docs/transaction-v1.md) for what v1 changes, its activation status, and which tools support it. - -## Major concepts - -**What v1 changes for the transaction.** The size limit rises from 1,232 to 4,096 bytes, which is what makes this example possible. The resource limits that legacy and v0 transactions set with ComputeBudget program instructions (compute unit limit, priority fee, loaded-accounts data size limit, heap size) move into a `TransactionConfig` in the message header, and any ComputeBudget instructions in a v1 transaction are ignored. The priority fee becomes a total in lamports rather than a price per compute unit. Unset config fields mean zero, not "use the default", so every v1 transaction has to set its compute unit limit and its loaded-accounts data size limit explicitly. The signatures move to the end of the wire format, so the first byte is the message version (`0x81` for v1). Address lookup tables are not supported: a transaction that needs them stays on v0. - -**What v1 changes for the program.** Nothing. The 3,000 byte document arrives as instruction data exactly as a 300 byte one would. - -**The document account.** A [PDA](https://solana.com/docs/references/terminology#program-derived-account-pda) with seeds `["document", payer]`, one per payer, created by the program with a size equal to the document and owned by the program. The payer pays the [rent](https://solana.com/docs/references/terminology#rent). - -## Lifecycle - -A payer signs a transaction carrying one instruction: the `STORE_DOCUMENT` discriminator byte followed by the document. The `store_document` handler checks that the payer signed, that the document is not empty, that the third account is the System Program, and that the second account is the payer's document PDA. It then creates the PDA through a signed CPI to the System Program, sized to the document, and copies the document in. - -## Setup - -Prerequisites: the [Agave](https://docs.anza.xyz/) toolchain (`cargo build-sbf`) and Rust. - -From this directory (`basics/transaction-v1/pinocchio/`): - -```bash -cargo build-sbf --manifest-path=./program/Cargo.toml -``` - -This writes the program binary to `target/deploy/transaction_v1_pinocchio_program.so`. - -This example is its own Cargo workspace rather than a member of the repository root workspace: the root pins LiteSVM 0.13.1, which predates v1, and the two LiteSVM versions cannot share a lockfile. `Cargo.toml` at this level explains the details. - -## Testing - -The Rust + [LiteSVM](https://www.litesvm.com/) tests load the `.so` built above. The binary is embedded at test-compile time, so rebuild after every program change or a stale `.so` silently tests old code. After building, run: - -```bash -cargo test --manifest-path=./program/Cargo.toml -``` - -LiteSVM 0.16.0 is the first release that executes v1 transactions. The suite covers: - -- `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. -- `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. -- `the_transaction_config_replaces_compute_budget_instructions`: a v1 transaction with a priority fee in the config. The payer is charged exactly rent plus the base fee plus that priority fee, and the wire format starts with the v1 version byte. -- `unset_config_fields_mean_zero_not_the_default`: an empty config fails before the program runs (zero bytes of account data may be loaded), and a config with only the data size limit fails on the program's first instruction (zero compute units). - -LiteSVM does not enforce either size limit (a validator's packet layer does), which is why the size tests check the serialized transaction themselves. - -## Sending v1 transactions to a real cluster - -The v1 format is live on mainnet beta since epoch 1035 (15 September 2026), and on devnet and testnet, behind the `enable_tx_v1` feature gate (`txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`). Sending one needs Agave 4.2 or later client crates and, on the RPC side, `maxSupportedTransactionVersion: 1` on every `getTransaction`, `getBlock` and `blockSubscribe` call, plus `encoding: "base64"` when sending, since base58 encoding stops at 1,232 bytes. See [docs/transaction-v1.md](../../../docs/transaction-v1.md) for the details and the client libraries that can build v1 transactions. diff --git a/basics/transaction-v1/pinocchio/program/Cargo.toml b/basics/transaction-v1/pinocchio/program/Cargo.toml deleted file mode 100644 index dc98ef043..000000000 --- a/basics/transaction-v1/pinocchio/program/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "transaction-v1-pinocchio-program" -version = "0.1.0" -edition = "2021" - -[dependencies] -pinocchio = { version = "0.10.2", features = ["cpi"] } -pinocchio-system = "0.5.0" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -custom-heap = [] -custom-panic = [] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } - -[dev-dependencies] -# 0.16.0 is the first LiteSVM that understands v1 transactions: it reads the -# compute unit limit, priority fee, loaded-accounts data size limit and heap -# size from the message's transaction config rather than from ComputeBudget -# instructions. -litesvm = "0.16.0" -solana-instruction = "3.4" -solana-keypair = "3.1" -# `solana_message::v1` (the v1 message, `TransactionConfig` and the 4,096 -# byte `MAX_TRANSACTION_SIZE`) first shipped in solana-message 4.2. -solana-message = "4.2" -solana-native-token = "3.0" -# `PACKET_DATA_SIZE`, the 1,232 byte limit legacy and v0 transactions keep. -solana-packet = "4.2" -solana-pubkey = "3.0" -solana-system-interface = "2.0.0" -solana-transaction = "4.1" -solana-transaction-error = "3.3" -# The SDK's serializer. `wincode::serialize` on a `VersionedTransaction` gives -# the exact bytes that would go over the wire, which is how the tests measure -# transaction size. -wincode = "0.5" diff --git a/basics/transaction-v1/pinocchio/program/src/lib.rs b/basics/transaction-v1/pinocchio/program/src/lib.rs deleted file mode 100644 index 7045885a7..000000000 --- a/basics/transaction-v1/pinocchio/program/src/lib.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Store a document of arbitrary size in a program-owned account, in one -//! instruction. -//! -//! The program itself has nothing to do with transaction v1: it reads the -//! accounts and instruction data it is given like any other program, and does -//! not know or care which transaction format carried them. What v1 changes is -//! how much a single instruction can carry: a 3,000 byte document is beyond -//! the 1,232 byte limit of a legacy or v0 transaction, so the tests send it -//! in a v1 transaction (up to 4,096 bytes) instead. - -#![no_std] - -use pinocchio::{ - cpi::{Seed, Signer}, - entrypoint, - error::ProgramError, - nostd_panic_handler, - sysvars::{rent::Rent, Sysvar}, - AccountView, Address, ProgramResult, -}; -use pinocchio_system::instructions::CreateAccount; - -entrypoint!(process_instruction); -nostd_panic_handler!(); - -/// First byte of the instruction data. Everything after it is the document. -pub const STORE_DOCUMENT: u8 = 0; - -/// Seed prefix of the document account. One document per payer. -pub const DOCUMENT_SEED: &[u8] = b"document"; - -fn process_instruction( - program_id: &Address, - accounts: &[AccountView], - instruction_data: &[u8], -) -> ProgramResult { - match instruction_data.split_first() { - Some((&STORE_DOCUMENT, document)) => store_document(program_id, accounts, document), - _ => Err(ProgramError::InvalidInstructionData), - } -} - -/// Create the payer's document PDA, sized to the document exactly, and copy -/// the document into it. Rent comes from the payer. -fn store_document( - program_id: &Address, - accounts: &[AccountView], - document: &[u8], -) -> ProgramResult { - let [payer, document_account, system_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if !payer.is_signer() { - return Err(ProgramError::MissingRequiredSignature); - } - if document.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - if !pinocchio_system::check_id(system_program.address()) { - return Err(ProgramError::IncorrectProgramId); - } - - // The client passes the PDA; derive it here so nobody can hand us some - // other account to write into. - let (expected_address, bump) = - Address::find_program_address(&[DOCUMENT_SEED, payer.address().as_ref()], program_id); - if document_account.address() != &expected_address { - return Err(ProgramError::InvalidSeeds); - } - - let lamports_required = Rent::get()?.try_minimum_balance(document.len())?; - - let bump_bytes = [bump]; - let seeds = [ - Seed::from(DOCUMENT_SEED), - Seed::from(payer.address().as_ref()), - Seed::from(&bump_bytes), - ]; - let signers = [Signer::from(&seeds)]; - - CreateAccount { - from: payer, - to: document_account, - lamports: lamports_required, - space: document.len() as u64, - owner: program_id, - } - .invoke_signed(&signers)?; - - document_account.try_borrow_mut()?.copy_from_slice(document); - - Ok(()) -} diff --git a/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs b/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs deleted file mode 100644 index 0e282c711..000000000 --- a/basics/transaction-v1/pinocchio/program/tests/test_transaction_v1.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! The program is exercised with both transaction formats: legacy for a small -//! document, v1 for one that would never fit in 1,232 bytes. The v1 tests also -//! cover the part of the format that is easy to get wrong: the transaction -//! config that replaces ComputeBudget instructions, whose unset fields mean -//! zero rather than "use the default". - -use { - litesvm::LiteSVM, - solana_instruction::{AccountMeta, Instruction}, - solana_keypair::{Keypair, Signer}, - solana_message::{v1, VersionedMessage}, - solana_native_token::LAMPORTS_PER_SOL, - solana_packet::PACKET_DATA_SIZE, - solana_pubkey::Pubkey, - solana_transaction::{versioned::VersionedTransaction, Transaction}, - solana_transaction_error::TransactionError, - transaction_v1_pinocchio_program::{DOCUMENT_SEED, STORE_DOCUMENT}, -}; - -/// What LiteSVM charges per signature, and what mainnet has charged since -/// genesis. -const BASE_FEE_LAMPORTS: u64 = 5_000; - -/// Enough for `create_account` plus the copy. Unlike a legacy transaction, a -/// v1 transaction has no 200,000 CU default to fall back on: leaving the -/// limit unset means zero. -const COMPUTE_UNIT_LIMIT: u32 = 20_000; - -/// The payer, the new account, the system program and the program itself -/// together load well under 64 KiB. Like the compute unit limit, this defaults -/// to zero when unset. -const LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024; - -/// A v1 transaction's config carries the resource limits that legacy and v0 -/// transactions express with ComputeBudget program instructions. Every test -/// that expects to succeed starts from this one. -fn transaction_config() -> v1::TransactionConfig { - v1::TransactionConfig::empty() - .with_compute_unit_limit(COMPUTE_UNIT_LIMIT) - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT) -} - -fn setup() -> (LiteSVM, Pubkey, Keypair) { - let mut svm = LiteSVM::new(); - let program_id = Pubkey::new_unique(); - // The .so is built into the workspace target/deploy by - // `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the - // project root). Rebuild after every program change: the binary is - // embedded at test-compile time, so a stale .so silently tests old code. - let program_bytes = include_bytes!("../../target/deploy/transaction_v1_pinocchio_program.so"); - svm.add_program(program_id, program_bytes).unwrap(); - - let payer = Keypair::new(); - svm.airdrop(&payer.pubkey(), 10 * LAMPORTS_PER_SOL).unwrap(); - (svm, program_id, payer) -} - -/// The instruction and the PDA it will create. -fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Instruction, Pubkey) { - let (document_address, _bump) = - Pubkey::find_program_address(&[DOCUMENT_SEED, payer.as_ref()], program_id); - let mut data = Vec::with_capacity(1 + document.len()); - data.push(STORE_DOCUMENT); - data.extend_from_slice(document); - let instruction = Instruction { - program_id: *program_id, - accounts: vec![ - AccountMeta::new(*payer, true), - AccountMeta::new(document_address, false), - AccountMeta::new_readonly(solana_system_interface::program::ID, false), - ], - data, - }; - (instruction, document_address) -} - -/// Compile a v1 message and sign it. `try_compile_with_config` is the v1 -/// counterpart of `Message::new_with_blockhash`; the config rides in the -/// message header, so there is no ComputeBudget instruction to append. -fn v1_transaction( - svm: &LiteSVM, - payer: &Keypair, - instructions: &[Instruction], - config: v1::TransactionConfig, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - &payer.pubkey(), - instructions, - svm.latest_blockhash(), - config, - ) - .unwrap(); - VersionedTransaction::try_new(VersionedMessage::V1(message), &[payer]).unwrap() -} - -/// The bytes that would be sent to an RPC node. -fn wire_size(transaction: &VersionedTransaction) -> usize { - wincode::serialize(transaction).unwrap().len() -} - -/// A document with a recognizable pattern, so a partial or shifted copy -/// would not pass the equality check. -fn sample_document(length: usize) -> Vec { - (0..length).map(|i| (i % 251) as u8).collect() -} - -#[test] -fn stores_a_document_too_large_for_a_legacy_transaction() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); - - // Over the legacy limit, under the v1 one. LiteSVM does not enforce either - // (a validator's packet layer does), so the test checks the size itself. - let size = wire_size(&transaction); - assert!( - size > PACKET_DATA_SIZE, - "{size} bytes fits a legacy transaction" - ); - assert!( - size <= v1::MAX_TRANSACTION_SIZE, - "{size} bytes is over the v1 limit" - ); - - svm.send_transaction(transaction).unwrap(); - - let account = svm.get_account(&document_address).unwrap(); - assert_eq!(account.owner, program_id); - assert_eq!(account.data, document); -} - -#[test] -fn a_legacy_transaction_still_works_for_a_small_document() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - - // Nothing about the program changed: the same instruction goes through the - // legacy format when it fits. - let transaction = Transaction::new_signed_with_payer( - &[instruction], - Some(&payer.pubkey()), - &[&payer], - svm.latest_blockhash(), - ); - assert!(wire_size(&VersionedTransaction::from(transaction.clone())) <= PACKET_DATA_SIZE); - - svm.send_transaction(transaction).unwrap(); - - assert_eq!(svm.get_account(&document_address).unwrap().data, document); -} - -#[test] -fn the_transaction_config_replaces_compute_budget_instructions() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(3_000); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - let rent = svm.minimum_balance_for_rent_exemption(document.len()); - - // A v1 priority fee is a total in lamports, not a price per compute unit. - let priority_fee_lamports = 10_000; - let config = transaction_config().with_priority_fee(priority_fee_lamports); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - - // v1 moved the signatures to the end, so the first byte on the wire is the - // message version rather than a signature count. 0x81 means v1. - assert_eq!(wincode::serialize(&transaction).unwrap()[0], v1::V1_PREFIX); - - let balance_before = svm.get_balance(&payer.pubkey()).unwrap(); - let metadata = svm.send_transaction(transaction).unwrap(); - let balance_after = svm.get_balance(&payer.pubkey()).unwrap(); - - assert!(metadata.compute_units_consumed <= u64::from(COMPUTE_UNIT_LIMIT)); - assert_eq!( - balance_before - balance_after, - rent + BASE_FEE_LAMPORTS + priority_fee_lamports - ); -} - -#[test] -fn unset_config_fields_mean_zero_not_the_default() { - let (mut svm, program_id, payer) = setup(); - let document = sample_document(500); - let (instruction, _) = store_document(&program_id, &payer.pubkey(), &document); - - // No loaded-accounts data size limit: the transaction fails before any - // program runs, because zero bytes of account data may be loaded. - let transaction = v1_transaction( - &svm, - &payer, - std::slice::from_ref(&instruction), - v1::TransactionConfig::empty(), - ); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert_eq!( - failure.err, - TransactionError::MaxLoadedAccountsDataSizeExceeded - ); - - // Data size limit set, compute unit limit not: the program starts with a - // budget of zero and fails on its first instruction. - let config = v1::TransactionConfig::empty() - .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); - let failure = svm.send_transaction(transaction).unwrap_err(); - assert!(matches!( - failure.err, - TransactionError::InstructionError(0, _) - )); - assert!( - failure - .meta - .logs - .iter() - .any(|log| log.contains("exceeded CUs")), - "{:?}", - failure.meta.logs - ); -} diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml b/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml index 59de918a7..fd99c51a8 100644 --- a/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml +++ b/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml @@ -27,6 +27,8 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs index 3afb86614..4d9af4105 100644 --- a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs +++ b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml index 5acbf1be6..27bc2c05c 100644 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml @@ -37,6 +37,7 @@ solana-native-token = "3.0.0" solana-signer = "3.0.0" solana-message = "4.2.4" solana-keccak-hasher = "3.0.0" +solana-kite = "0.5.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs index a2ece44da..e5a93b83e 100644 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs @@ -12,16 +12,18 @@ //! 6. Call our program's `burn_cnft`, signed by `leaf_owner`, and assert the //! transaction succeeds and a second burn fails (leaf already zeroed). +mod transaction_v1; + +use transaction_v1::v1_transaction; + use { borsh::BorshSerialize, litesvm::LiteSVM, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -245,10 +247,7 @@ fn send( payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); + let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); svm.send_transaction(tx).map(|_| ()).map_err(Box::new) } diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml index 123934404..042a0eeb0 100644 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml @@ -40,3 +40,4 @@ solana-native-token = "3.0.0" solana-signer = "3.0.0" solana-message = "4.2.4" solana-keccak-hasher = "3.0.0" +solana-kite = "0.5.0" diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs index a57f22791..f04f92cff 100644 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs @@ -25,6 +25,10 @@ //! proof accounts fails with `VaultError::ProofLengthMismatch` instead //! of panicking inside `split_at` +mod transaction_v1; + +use transaction_v1::v1_transaction; + use { borsh::BorshSerialize, cnft_vault::error::VaultError, @@ -32,10 +36,8 @@ use { solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -255,10 +257,7 @@ fn send( payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); + let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); svm.send_transaction(tx).map(|_| ()).map_err(Box::new) } diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml index de667a06b..a8c4a4e0c 100644 --- a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml @@ -42,3 +42,4 @@ solana-native-token = "3.0.0" solana-signer = "3.0.0" solana-message = "4.2.4" solana-keccak-hasher = "3.0.0" +solana-kite = "0.5.0" diff --git a/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs b/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs index 36be2b733..33dddf476 100644 --- a/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs +++ b/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs @@ -25,16 +25,18 @@ //! asserting success. A second `verify` with a tampered data_hash must //! fail. +mod transaction_v1; + +use transaction_v1::v1_transaction; + use { borsh::BorshSerialize, litesvm::LiteSVM, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -284,10 +286,7 @@ fn send( payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); + let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); svm.send_transaction(tx).map(|_| ()).map_err(Box::new) } diff --git a/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs b/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/docs/transaction-v1.md b/docs/transaction-v1.md deleted file mode 100644 index 49ca6acae..000000000 --- a/docs/transaction-v1.md +++ /dev/null @@ -1,146 +0,0 @@ -# Transaction v1 and this repository - -Solana's v1 transaction format ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), sized by [SIMD-0296](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md)) raises the maximum transaction size from 1,232 to 4,096 bytes. This page is the repository's view of it: what changes, where it is live, which of the tools these examples build on can send it, and what that means for the examples. The worked example is [`basics/transaction-v1/`](../basics/transaction-v1/anchor/), in Anchor v2, Anchor v1, Pinocchio and native Rust. - -Status is as of 22 September 2026. - -## What changes - -| | Legacy and v0 | v1 | -| --- | --- | --- | -| Maximum size | 1,232 bytes | 4,096 bytes | -| Compute unit limit, heap size, loaded-accounts data size limit | ComputeBudget program instructions | `TransactionConfig` fields in the message header | -| Priority fee | `SetComputeUnitPrice`, in micro-lamports per compute unit | `TransactionConfig::priority_fee`, a total in lamports | -| Unset resource limits | Runtime defaults (200,000 compute units per instruction, 64 MiB of account data) | Zero. Only the heap size falls back, to 32 KiB | -| ComputeBudget instructions present | Applied | Ignored | -| Address lookup tables | v0 only | Not supported | -| Account addresses | Bounded by size (v0: up to 64 with lookup tables) | Up to 64, all inline, no duplicates | -| Instructions | Bounded by size | Up to 64 | -| Signatures | Bounded by size | Up to 12 | -| Wire format | Signature count first | Message version byte first (`0x81`), signatures last | - -Two of these bite in practice. A v1 transaction that does not set its compute unit limit fails on its first instruction, and one that does not set its loaded-accounts data size limit fails before any program runs, with `MaxLoadedAccountsDataSizeExceeded`. The example's `unset_config_fields_mean_zero_not_the_default` test shows both. - -The other one is the lookup tables. v1 is bigger, not more flexible: a transaction that touches more accounts than fit inline in 4,096 bytes still needs v0 and a lookup table. The vault strategy example's deposit, with five accounts per basket asset, is that case. - -## What changes for programs - -Nothing. A program sees the same accounts and the same instruction data whether a legacy, v0 or v1 transaction carried them, so no program in this repository, in any framework, needs a change to be called through v1. The one visible difference is to programs that read the transaction's ComputeBudget instructions through the instructions sysvar, for instance to detect a priority fee: a v1 transaction has none, and the config that replaced them is not exposed to programs. - -## What changes for tests and clients - -Everything that builds a transaction has to know the format. That is the test suites here, and every client. - -### Sending a v1 transaction in Rust - -`solana-message` 4.2 or later has the `v1` module. The example's tests do exactly this against LiteSVM: - -```rust -use solana_message::{v1, VersionedMessage}; -use solana_transaction::versioned::VersionedTransaction; - -let config = v1::TransactionConfig::empty() - .with_compute_unit_limit(20_000) - .with_loaded_accounts_data_size_limit(64 * 1024) - // A total in lamports, not a price per compute unit. - .with_priority_fee(5_000); - -let message = v1::Message::try_compile_with_config( - &payer.pubkey(), - &[instruction], - recent_blockhash, - config, -)?; -let transaction = VersionedTransaction::try_new(VersionedMessage::V1(message), &[&payer])?; -``` - -`wincode::serialize(&transaction)` gives the wire bytes, and `v1::MAX_TRANSACTION_SIZE` (4,096) and `solana_packet::PACKET_DATA_SIZE` (1,232) are the two limits to compare against. LiteSVM enforces neither, so a test that cares about size checks it itself. - -### Sending a v1 transaction in TypeScript - -`@solana/kit` 8.0.0 or later. The config setter replaces the ComputeBudget instruction helpers: - -```typescript -const message = pipe( - createTransactionMessage({ version: 1 }), - (m) => setTransactionMessageFeePayerSigner(payer, m), - (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - (m) => appendTransactionMessageInstruction(instruction, m), - (m) => - setTransactionMessageConfig( - { - computeUnitLimit: 20_000, - loadedAccountsDataSizeLimit: 64 * 1024, - priorityFeeLamports: 5_000n, - }, - m, - ), -); -``` - -Send and simulate with `encoding: "base64"`: base58 encoding stops at 1,232 bytes. - -### Reading v1 transactions - -Every `getTransaction`, `getBlock` and `blockSubscribe` call needs `maxSupportedTransactionVersion: 1` (the integer, not the string). Without it, `getTransaction` returns error `-32015`, `getBlock` fails for the whole block if it holds one v1 transaction, and `blockSubscribe` emits `block: null`. Indexers that scan for ComputeBudget instructions to read fees see zero for v1 transactions and have to read `transactionConfig` instead. - -## Where v1 is live - -The feature gate is `enable_tx_v1`, `txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL`, shipped in Agave 4.2. - -| Cluster | Status | -| --- | --- | -| `solana-test-validator` (Agave 4.2+) and Surfpool 1.5+ | Active at genesis | -| Testnet | Active since epoch 1025 | -| Devnet | Active | -| Mainnet beta | Active since epoch 1035 (15 September 2026, about 01:00 UTC). The first target, 9 September, slipped by one epoch | - -To check any cluster, including a private one: - -```bash -solana -u feature status txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL -``` - -## Tool support - -What the toolchains this repository uses can do with v1, checked against the versions the examples pin. - -| Tool | Used here as | v1 | -| --- | --- | --- | -| Agave CLI and `solana-test-validator` | CI installs 3.1.14 | 4.2.0 and later. The LiteSVM tests do not need it | -| `solana-message`, `solana-transaction` (Rust) | 4.2 / 4.1 in the Anchor v1 tests and the example; 3.x in the root workspace | `solana-message` 4.2 and later | -| [LiteSVM](https://github.com/LiteSVM/litesvm) | 0.16.0 in the Anchor v1 examples and the example; 0.13.1 in the root workspace and two standalone native examples | 0.16.0 (24 August 2026) and later. Reads the compute budget from the config | -| [Surfpool](https://surfpool.run/) | `anchor test` and `anchor localnet` default validator | 1.5 and later | -| `@solana/kit` | Not used by the tests | 8.0.0 and later | -| `@solana/web3.js` 1.x | Two wallet-adapter demo apps | Reads v1 from 1.99.0. Cannot build one. `@solana/web3.js` 3.0.0-rc.3 and later can | -| Anchor v2 programs (`anchor-lang` 2.0.0-rc.1) | 56 examples | No change needed | -| `anchor-v2-testing` (the Anchor v2 LiteSVM harness) | Every Anchor v2 example but this one | Not yet. Pins LiteSVM `=0.13.1`, on the pinned revision and on the tip of `anchor-next`, unchanged since 17 August. Not on crates.io | -| Anchor v1 programs (`anchor-lang` 1.2.0) | 56 examples | No change needed | -| `@coral-xyz/anchor` (Anchor's TypeScript client) | The vault strategy web app | Not yet. Depends on `@solana/web3.js` 1.x, which cannot build a v1 transaction | -| Quasar programs (`quasar-lang` 0.1.0) | 56 examples | No change needed | -| `quasar-test` and `quasar-svm` (Quasar's test harness and VM) | Every Quasar example | Not yet. `quasar-svm` 0.1.0 (March, still the only release) is on `solana-message` 3.x and its `master` branch on 4.1, one release short of the `v1` module. No commits since 14 July | -| Pinocchio, native Rust and sBPF assembly programs | 47 examples | No change needed | -| [`solana-kite`](https://github.com/solanakite/kite-rust) | 0.5.0 in the Anchor v1 tests; 0.4.0 in the root workspace | On LiteSVM 0.16 from 0.5.0 (8 September 2026). Its `send_transaction_from_instructions` builds legacy transactions, so a v1 one is built by hand | - -## What that means for the examples - -Only `basics/transaction-v1/` sends v1 transactions. Every other example's tests still send legacy transactions, and they keep passing, because nothing on the program side changed. - -Where the rest of the tests stand: - -1. **Anchor v1 examples**: on LiteSVM 0.16 through `solana-kite` 0.5.0, since the move to Anchor 1.2.0. Any of them could send a v1 transaction today; none does, because kite builds legacy transactions. -2. **Anchor v2, native and Pinocchio examples in the root workspace**: on LiteSVM 0.13.1 and `solana-kite` 0.4.0. They cannot move until `anchor-v2-testing` drops its `=0.13.1` pin, because 0.13.1 and 0.16.0 pin different `solana-instruction` 3.x patch releases and one lockfile cannot hold both. The alternative is for the Anchor v2 examples to call LiteSVM directly, as this example does, at the cost of `anchor test --profile`, `anchor debugger` and `anchor coverage`. -3. **Quasar examples**: need `quasar-svm` on `solana-message` 4.2 or later, and a way for a `#[quasar_test]` test to choose the transaction format. Neither exists yet, so there is no Quasar variant of the example. - -That is also why the example's four directories are standalone Cargo workspaces, deliberately absent from the root `Cargo.toml` members list. - -## Sources - -- [Larger transaction sizes](https://solana.com/upgrades/larger-transaction-sizes), the upgrade page. -- [SIMD-0385: Transaction V1](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md) and [SIMD-0296: Larger transactions](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md). -- [solana-foundation/transaction-v1-examples](https://github.com/solana-foundation/transaction-v1-examples), runnable Rust, TypeScript, Python and Go clients and indexers, with a minimum-version table. -- [The `transactions-v1` reference in solana-dev-skill](https://github.com/solana-foundation/solana-dev-skill/blob/main/skills/solana-dev/references/transactions-v1.md). -- [Feature gate tracker schedule](https://github.com/anza-xyz/agave/wiki/Feature-Gate-Tracker-Schedule), for features still pending activation. -- [solana-foundation/solana-com#2078](https://github.com/solana-foundation/solana-com/pull/2078), which moved the upgrade page's mainnet date to the start of epoch 1035. -- [LiteSVM 0.16.0](https://github.com/LiteSVM/litesvm/releases/tag/v0.16.0), the release that added v1 support. -- [Solana v1 transactions explained](https://www.quicknode.com/blog/solana-v1-transactions-explained), on the Quicknode blog. diff --git a/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml b/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml index da0f66cfc..c97dcfcb6 100644 --- a/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml +++ b/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml @@ -36,6 +36,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs b/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs index 0ed7cbf18..a98ebd8be 100644 --- a/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs +++ b/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ prelude::Clock, @@ -11,8 +15,7 @@ use { solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, - get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, SolanaKiteError, + get_token_account_balance, mint_tokens_to_token_account, SolanaKiteError, }, solana_signer::Signer, }; diff --git a/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs b/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/escrow/anchor-v1/programs/escrow/Cargo.toml b/finance/escrow/anchor-v1/programs/escrow/Cargo.toml index 46d88511e..3026b5517 100644 --- a/finance/escrow/anchor-v1/programs/escrow/Cargo.toml +++ b/finance/escrow/anchor-v1/programs/escrow/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs b/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs index b63186212..e0c011ce6 100644 --- a/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs +++ b/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ error::ErrorCode, @@ -9,7 +13,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs b/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml b/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml index 9a450cbb8..e5ed3056f 100644 --- a/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml +++ b/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml @@ -35,6 +35,8 @@ borsh = "1.6.1" # "no-entrypoint" here removes theirs via feature unification. spl-token = { version = "9.0.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] } +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs index 42697bfb1..df903e5c2 100644 --- a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{clock::Clock, instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +14,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/lending/anchor-v1/programs/lending/Cargo.toml b/finance/lending/anchor-v1/programs/lending/Cargo.toml index b680f61ab..23ee2e53f 100644 --- a/finance/lending/anchor-v1/programs/lending/Cargo.toml +++ b/finance/lending/anchor-v1/programs/lending/Cargo.toml @@ -34,6 +34,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs index 39f141eeb..5a6e4438c 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs @@ -18,10 +18,14 @@ use litesvm::LiteSVM; use solana_keypair::Keypair; use solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, }; use solana_signer::Signer; +pub mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use lending::constants::{ BPS_DENOMINATOR, LENDING_MARKET_SEED, LIQUIDITY_VAULT_SEED, MINIMUM_SHARES, OBLIGATION_SEED, OBLIGATION_SHARE_VAULT_SEED, PRICE_FEED_SEED, RESERVE_SEED, SHARE_MINT_SEED, diff --git a/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs b/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/lending/anchor-v1/programs/lending/tests/test_security.rs b/finance/lending/anchor-v1/programs/lending/tests/test_security.rs index 4cbb02390..b6e5a31ae 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/test_security.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/test_security.rs @@ -67,7 +67,7 @@ fn non_owner_cannot_write_market_price_feed() { } .data(), }; - let result = solana_kite::send_transaction_from_instructions( + let result = common::transaction_v1::send_transaction_from_instructions( &mut env.svm, vec![instruction], &[&attacker], diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml b/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml index 8cd4c422f..e6af2488a 100644 --- a/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml +++ b/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml @@ -35,6 +35,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs index 026eec364..ccdd39e08 100644 --- a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs +++ b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -13,7 +17,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/options/anchor-v1/programs/options/tests/test_options.rs b/finance/options/anchor-v1/programs/options/tests/test_options.rs index e44a4e53f..4e60ff29e 100644 --- a/finance/options/anchor-v1/programs/options/tests/test_options.rs +++ b/finance/options/anchor-v1/programs/options/tests/test_options.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::{send_transaction_from_instructions, v1_transaction}; + use { anchor_lang::{ error::{ErrorCode as AnchorErrorCode, ERROR_CODE_OFFSET}, @@ -14,11 +18,8 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, - solana_message::Message, solana_signer::Signer, - solana_transaction::Transaction, }; // The underlying is NVDAx (tokenized NVIDIA stock), which has 8 decimals, @@ -350,9 +351,7 @@ impl Venue { instruction: Instruction, signer: &Keypair, ) -> Result { - let message = Message::new(&[instruction], Some(&signer.pubkey())); - let mut transaction = Transaction::new_unsigned(message); - transaction.sign(&[signer], self.svm.latest_blockhash()); + let transaction = v1_transaction(&self.svm, &[instruction], &[signer], &signer.pubkey()); self.svm .send_transaction(transaction) .map_err(|failed| format!("{:?}", failed.err)) @@ -986,7 +985,10 @@ fn test_put_writer_without_an_underlying_account_writes_and_cancels() { let option = venue.write_put(&carol); assert_eq!(venue.balance(&carol.underlying), 0); - assert_eq!(venue.balance(&carol.quote), STANDARD_USDC - PUT_STRIKE_AMOUNT); + assert_eq!( + venue.balance(&carol.quote), + STANDARD_USDC - PUT_STRIKE_AMOUNT + ); let option_rent = venue.lamports(&option); let carol_lamports_before_cancel = venue.lamports(&carol.pubkey()); diff --git a/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs b/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/order-book/anchor-v1/programs/order-book/Cargo.toml b/finance/order-book/anchor-v1/programs/order-book/Cargo.toml index 41b452835..68959ebb6 100644 --- a/finance/order-book/anchor-v1/programs/order-book/Cargo.toml +++ b/finance/order-book/anchor-v1/programs/order-book/Cargo.toml @@ -40,6 +40,7 @@ solana-kite = "0.5.0" # each one used, which solana-kite's helper does not return. Same versions # solana-kite builds against. solana-transaction = "4.1.5" +solana-message = "4.2.4" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs index d8c93fd40..a8b765a56 100644 --- a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs @@ -13,6 +13,10 @@ //! `close_market_user` give a finished order's and an idle user's rent back //! to the owner, and refuse while anything still rests or is owed. +mod transaction_v1; + +use transaction_v1::{send_transaction_from_instructions, v1_transaction}; + use { anchor_lang::{ solana_program::{ @@ -27,7 +31,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -2340,12 +2343,7 @@ fn best_ask_depth(svm: &LiteSVM, order_book: &Pubkey) -> usize { // Send one instruction and return the compute units it used. fn send_and_measure(svm: &mut LiteSVM, instruction: Instruction, signer: &Keypair) -> u64 { - let transaction = solana_transaction::Transaction::new_signed_with_payer( - &[instruction], - Some(&signer.pubkey()), - &[signer], - svm.latest_blockhash(), - ); + let transaction = v1_transaction(svm, &[instruction], &[signer], &signer.pubkey()); svm.send_transaction(transaction) .unwrap() .compute_units_consumed diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs b/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml index dd3247f6a..230012008 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml @@ -45,6 +45,8 @@ borsh = "1.6.1" # The LiteSVM tests load the compiled mock oracle program; depending on the # crate here lets the tests reuse its instruction-argument types and program ID. mock_price_feed = { path = "../mock-price-feed", features = ["no-entrypoint"] } +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs index b11456879..f664cf698 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ error::ErrorCode as AnchorErrorCode, @@ -19,7 +23,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml b/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml index 24c49330c..50f7a6bce 100644 --- a/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml +++ b/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml @@ -42,6 +42,8 @@ borsh = "1.6.1" # The LiteSVM tests load the compiled mock oracle program; depending on the # crate here lets the tests reuse its instruction-argument types and program ID. mock_price_feed = { path = "../mock-price-feed", features = ["no-entrypoint"] } +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs index 808eaa8d2..5b8df8dc6 100644 --- a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ error::{ErrorCode as AnchorErrorCode, ERROR_CODE_OFFSET}, @@ -14,7 +18,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml b/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml index bde1c30bc..4a821785f 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml +++ b/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml @@ -32,6 +32,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs b/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs index d43718f4d..1330d95b2 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs +++ b/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -8,7 +12,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs b/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml b/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml index 63f729b3d..92bb99daf 100644 --- a/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml +++ b/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml @@ -30,6 +30,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs b/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs index 4f393daa9..631565511 100644 --- a/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs +++ b/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, send_transaction_from_instructions}, + solana_kite::create_wallet, solana_signer::Signer, }; diff --git a/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs b/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml index e728e1082..cf6534329 100644 --- a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml +++ b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml @@ -34,6 +34,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs index 900aa1af6..a6128f9a0 100644 --- a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs +++ b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +14,6 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, - send_transaction_from_instructions, }, solana_signer::Signer, }; diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml b/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml index 677346833..144ec9565 100644 --- a/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml +++ b/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs index 9dabc4c89..923e9ee70 100644 --- a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs +++ b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, + solana_kite::{create_wallet, get_token_account_balance}, solana_signer::Signer, }; diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml b/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml index 37dd9b203..4d412648a 100644 --- a/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml +++ b/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs index 249860449..e3d4db767 100644 --- a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs +++ b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, + solana_kite::{create_wallet, get_token_account_balance}, solana_signer::Signer, }; diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml index 80ee1de4d..f32cc0120 100644 --- a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml +++ b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs index 4503a8385..b6ff2deeb 100644 --- a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs +++ b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, + solana_kite::{create_wallet, get_token_account_balance}, solana_signer::Signer, }; diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml b/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml index 4d1dc1e3e..12c81f3bc 100644 --- a/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml +++ b/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs index 3ad62418b..49e2faa86 100644 --- a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs +++ b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, send_transaction_from_instructions, + assert_token_account_balance, create_wallet, token_extensions::{get_token_extensions_account_address, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml index 727b37608..35360c239 100644 --- a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml +++ b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs index 28a46ccf3..a4e858d69 100644 --- a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs +++ b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, send_transaction_from_instructions, + assert_token_account_balance, create_wallet, token_extensions::{ create_token_extensions_mint, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml index a96ab9c3a..d6fd1c65c 100644 --- a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml +++ b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs index 72265cf5c..e3c5ebecf 100644 --- a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs +++ b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, send_transaction_from_instructions, + assert_token_account_balance, create_wallet, token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml b/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml index f92fc3e15..44ba9db14 100644 --- a/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml +++ b/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs b/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs index 114d7a443..6c807f363 100644 --- a/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs +++ b/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs @@ -1,13 +1,14 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, - }, + solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs b/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml index 99cd6d7b1..9f8fefb57 100644 --- a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml +++ b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs index dd5ad978c..9cf895769 100644 --- a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs +++ b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{create_token_extensions_mint, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml index cd239276b..624099607 100644 --- a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml +++ b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml @@ -30,6 +30,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs index 59fee9f80..0e3da857c 100644 --- a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs +++ b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,10 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, - }, + solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml index b1c82c0a3..5d551b786 100644 --- a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml +++ b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml @@ -29,6 +29,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs index c611a9da1..19af4a044 100644 --- a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs +++ b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, send_transaction_from_instructions, + assert_token_account_balance, create_wallet, token_extensions::{ create_token_extensions_mint, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml index 54a310ede..3991d215d 100644 --- a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml +++ b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml @@ -31,6 +31,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs index 8439e9d3c..8436cbe86 100644 --- a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs +++ b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,10 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, - }, + solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml index e88db0c2b..7b2f615f3 100644 --- a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml +++ b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml @@ -26,3 +26,5 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs index 81e724d01..344e6b54d 100644 --- a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs +++ b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,10 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, - }, + solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml index 72af552c9..1e6d79e4e 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml @@ -44,6 +44,8 @@ solana-signer = "3.0.0" solana-instruction = "3.0.0" solana-pubkey = "3.0.0" solana-kite = "0.5.0" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs index ef982a143..36df89dd5 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs @@ -19,6 +19,10 @@ //! (the crate constant) rather than a hardcoded literal, so the test keeps //! working after the id is regenerated. +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ prelude::Pubkey, solana_program::system_program, InstructionData, ToAccountMetas, @@ -26,7 +30,7 @@ use { litesvm::LiteSVM, solana_instruction::Instruction, solana_keypair::Keypair, - solana_kite::{create_wallet, get_pda_and_bump, send_transaction_from_instructions, Seed}, + solana_kite::{create_wallet, get_pda_and_bump, Seed}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml index eedc94848..a4aef0977 100644 --- a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml +++ b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml @@ -26,3 +26,5 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs index 3a67f7412..5c12d518f 100644 --- a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs +++ b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml index 0363c3390..25702cfd2 100644 --- a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml +++ b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml @@ -30,6 +30,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs index 20d0f8acb..72e90235c 100644 --- a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs +++ b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, send_transaction_from_instructions, + assert_token_account_balance, create_wallet, token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml index e989081b9..891b2326a 100644 --- a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml +++ b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml @@ -26,3 +26,5 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs index 2d7de4cbd..28a3ac4a8 100644 --- a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs +++ b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{ @@ -10,7 +14,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml index a0ed6e3a1..ba44d62c8 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml @@ -32,6 +32,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 39de57ed7..a8a62e8bd 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -7,7 +11,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml index 9dde9fefd..bc5ad66eb 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml @@ -42,6 +42,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs index d63851fa5..20bc2b97b 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,10 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, - }, + solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, solana_signer::Signer, }; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml index a0ed6e3a1..ba44d62c8 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml @@ -32,6 +32,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs index a09de2069..820227e2e 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -7,7 +11,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml index a0ed6e3a1..ba44d62c8 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml @@ -32,6 +32,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 99a5a2e8a..2df6e8174 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml index d001009a9..aa607c4c8 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml @@ -37,6 +37,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 04128be8f..0c5614063 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_mint, MintExtension, TOKEN_EXTENSIONS_PROGRAM_ID, }, diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml index d300dc792..d95456846 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml @@ -32,6 +32,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs index 3c3de0d08..b9f89c2c5 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml index 9af5af5ea..ec2529de7 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml @@ -32,6 +32,8 @@ solana-kite = "0.5.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 14ddbfdb8..c59fd8c40 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -6,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, send_transaction_from_instructions, + create_wallet, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml b/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml index 0c3ce64fb..6e1371a12 100644 --- a/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml +++ b/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs b/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs index 4d213c9fb..b393727bb 100644 --- a/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs +++ b/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, + solana_kite::{create_wallet, get_token_account_balance}, solana_signer::Signer, }; diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs b/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml index b388d4bb1..7b1a58886 100644 --- a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml +++ b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml @@ -29,6 +29,8 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.5.0" borsh = "1.6.1" +solana-message = "4.2.4" +solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs index 34479b943..d2769ba36 100644 --- a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs +++ b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs @@ -1,3 +1,7 @@ +mod transaction_v1; + +use transaction_v1::send_transaction_from_instructions; + use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -5,7 +9,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, + solana_kite::{create_wallet, get_token_account_balance}, solana_signer::Signer, }; diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs new file mode 100644 index 000000000..3450a5eb7 --- /dev/null +++ b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs @@ -0,0 +1,94 @@ +//! Send this example's test transactions in Solana's v1 transaction format +//! (SIMD-0385), live on mainnet since epoch 1035. +//! +//! `send_transaction_from_instructions` has the same name, arguments and error +//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps +//! one import and nothing else. Kite's version builds a legacy transaction. +//! +//! A v1 transaction carries its resource limits in a `TransactionConfig` in the +//! message instead of in ComputeBudget instructions, and a limit it leaves +//! unset is zero rather than the legacy default. Every transaction built here +//! asks for what a legacy transaction gets without ComputeBudget instructions, +//! so the programs under test see the same budget they always did. + +// Each test crate in this directory compiles its own copy of this module and +// uses only what it needs from it. +#![allow(dead_code)] + +use { + anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::SolanaKiteError, + solana_message::{v1, VersionedMessage}, + solana_signer::Signer, + solana_transaction::versioned::VersionedTransaction, +}; + +/// The default compute unit limit of a legacy transaction is 200,000 per +/// instruction. +const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; + +/// The most any transaction may ask for, and the cap on the legacy default. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; + +/// 64 MiB: the most account data any transaction may load, and the legacy +/// default. +const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; + +/// The config a legacy transaction with these instructions would get by +/// default. +pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { + let compute_unit_limit = u32::try_from(instruction_count) + .unwrap_or(u32::MAX) + .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) + .min(MAX_COMPUTE_UNIT_LIMIT); + v1::TransactionConfig::empty() + .with_compute_unit_limit(compute_unit_limit) + .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) +} + +/// Compile and sign a v1 transaction. Panics if the instructions do not fit +/// a v1 message or the signers do not match it, as kite's legacy builder does. +pub fn v1_transaction( + svm: &LiteSVM, + instructions: &[Instruction], + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> VersionedTransaction { + let message = v1::Message::try_compile_with_config( + fee_payer, + instructions, + svm.latest_blockhash(), + transaction_config(instructions.len()), + ) + .expect("instructions do not fit in a v1 message"); + + // A legacy transaction accepts the same keypair twice (a fee payer that is + // also a named signer, say); a v1 one wants each signer once. + let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); + for signer in signers { + if !unique_signers + .iter() + .any(|seen| seen.pubkey() == signer.pubkey()) + { + unique_signers.push(signer); + } + } + + VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) + .expect("signers do not match the transaction") +} + +/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. +pub fn send_transaction_from_instructions( + svm: &mut LiteSVM, + instructions: Vec, + signers: &[&Keypair], + fee_payer: &Pubkey, +) -> Result<(), SolanaKiteError> { + let transaction = v1_transaction(svm, &instructions, signers, fee_payer); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) +} From b62092c3cf26f397cbf6f9b561d6f00902acd034 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 14 Oct 2026 21:20:04 +0000 Subject: [PATCH 3/4] Send v1 transactions through solana-kite 0.6.0 kite 0.6.0 builds every transaction as v1, including the setup transactions its token, Token Extensions and transfer hook helpers send. The 57 copies of the tests/transaction_v1 module, which stood in for kite's legacy sender, are gone, and each test file imports from kite again. kite 0.6's senders and helpers take a new last argument, compute_unit_limit: Option. Every call passes None, so kite simulates the transaction and sends it with the compute units the simulation used. The six tests that read a transaction's metadata (logs, compute units, a program error code) build it with kite's build_transaction_from_instructions and get_transaction_config, with solana-compute-budget's MAX_COMPUTE_UNIT_LIMIT, and send it through LiteSVM. They no longer use solana-message or solana-transaction directly. In the transaction v1 example, v1_transaction becomes build_v1_transaction, and the sample document's pattern length is a named constant. Claude-Session: https://claude.ai/code/session_014EotgxjmAGtATDFVnBtjXj --- CHANGELOG.md | 34 +-- CONTRIBUTING.md | 10 +- README.md | 2 +- .../anchor-program-example/Cargo.toml | 4 +- .../tests/test_account_data.rs | 7 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-program-example/Cargo.toml | 4 +- .../tests/test_checking_accounts.rs | 8 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/close-account/Cargo.toml | 4 +- .../close-account/tests/test_close_account.rs | 10 +- .../close-account/tests/transaction_v1/mod.rs | 94 ------ .../programs/counter_anchor/Cargo.toml | 4 +- .../counter_anchor/tests/test_counter.rs | 22 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/create-system-account/Cargo.toml | 4 +- .../tests/test_create_account.rs | 7 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/hand/Cargo.toml | 4 +- .../programs/hand/tests/test_hand.rs | 11 +- .../programs/hand/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/lever/Cargo.toml | 4 +- .../programs/lever/tests/test_lever.rs | 20 +- .../lever/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/favorites/Cargo.toml | 4 +- .../favorites/tests/test_favorites.rs | 28 +- .../favorites/tests/transaction_v1/mod.rs | 94 ------ .../programs/hello-solana/Cargo.toml | 8 +- .../programs/hello-solana/tests/test_hello.rs | 21 +- .../hello-solana/tests/transaction_v1/mod.rs | 94 ------ .../anchor-program-example/Cargo.toml | 4 +- .../tests/test_pda_rent_payer.rs | 11 +- .../tests/transaction_v1/mod.rs | 94 ------ .../processing-instructions/Cargo.toml | 4 +- .../tests/test_processing_instructions.rs | 10 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-program-example/Cargo.toml | 4 +- .../tests/test_program_derived_addresses.rs | 28 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/pythexample/Cargo.toml | 4 +- .../programs/pythexample/tests/test_pyth.rs | 34 ++- .../pythexample/tests/transaction_v1/mod.rs | 94 ------ .../programs/anchor-realloc/Cargo.toml | 4 +- .../anchor-realloc/tests/test_realloc.rs | 13 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/rent-example/Cargo.toml | 4 +- .../programs/rent-example/tests/test_rent.rs | 7 +- .../rent-example/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/carnival/Cargo.toml | 4 +- .../programs/carnival/tests/test_carnival.rs | 30 +- .../carnival/tests/transaction_v1/mod.rs | 94 ------ basics/transaction-v1/anchor-v1/README.md | 2 +- .../programs/transaction-v1/Cargo.toml | 5 +- .../tests/test_transaction_v1.rs | 18 +- .../programs/transfer-sol/Cargo.toml | 4 +- .../transfer-sol/tests/test_transfer_sol.rs | 37 ++- .../transfer-sol/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/cnft-burn/Cargo.toml | 9 +- .../programs/cnft-burn/tests/test_burn.rs | 21 +- .../cnft-burn/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/cnft-vault/Cargo.toml | 9 +- .../programs/cnft-vault/tests/test_vault.rs | 21 +- .../cnft-vault/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/cutils/Cargo.toml | 9 +- .../programs/cutils/tests/test_cutils.rs | 21 +- .../cutils/tests/transaction_v1/mod.rs | 94 ------ .../programs/betting-market/Cargo.toml | 4 +- .../tests/test_betting_market.rs | 111 ++++++- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/escrow/Cargo.toml | 4 +- .../programs/escrow/tests/test_escrow.rs | 34 ++- .../escrow/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/fundraiser/Cargo.toml | 4 +- .../fundraiser/tests/test_fundraiser.rs | 20 +- .../fundraiser/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/lending/Cargo.toml | 4 +- .../programs/lending/tests/common/mod.rs | 40 ++- .../lending/tests/common/transaction_v1.rs | 94 ------ .../lending/tests/test_deposit_redeem.rs | 1 + .../programs/lending/tests/test_security.rs | 3 +- .../programs/managed-fund/Cargo.toml | 4 +- .../managed-fund/tests/managed_fund.rs | 226 ++++++++++---- .../managed-fund/tests/transaction_v1/mod.rs | 94 ------ .../programs/mock-swap-router/Cargo.toml | 2 +- .../anchor-v1/programs/options/Cargo.toml | 15 +- .../programs/options/tests/test_options.rs | 34 ++- .../options/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/order-book/Cargo.toml | 12 +- .../order-book/tests/test_order_book.rs | 282 ++++++++++++++---- .../order-book/tests/transaction_v1/mod.rs | 94 ------ .../programs/perpetual-futures/Cargo.toml | 4 +- .../tests/test_perpetual_futures.rs | 24 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/prop-amm/Cargo.toml | 4 +- .../programs/prop-amm/tests/test_prop_amm.rs | 36 ++- .../prop-amm/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/token-swap/Cargo.toml | 4 +- .../programs/token-swap/tests/test_swap.rs | 115 +++++-- .../token-swap/tests/transaction_v1/mod.rs | 94 ------ .../programs/create-token/Cargo.toml | 4 +- .../create-token/tests/test_create_token.rs | 8 +- .../create-token/tests/transaction_v1/mod.rs | 94 ------ .../external-delegate-token-master/Cargo.toml | 4 +- .../tests/test_external_delegate.rs | 23 +- .../tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/nft-minter/Cargo.toml | 4 +- .../nft-minter/tests/test_nft_minter.rs | 7 +- .../nft-minter/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/mint-nft/Cargo.toml | 4 +- .../mint-nft/tests/test_nft_operations.rs | 13 +- .../mint-nft/tests/transaction_v1/mod.rs | 94 ------ .../programs/token-minter/Cargo.toml | 4 +- .../token-minter/tests/test_pda_mint.rs | 10 +- .../token-minter/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/basics/Cargo.toml | 4 +- .../programs/basics/tests/test_basics.rs | 46 ++- .../basics/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/cpi-guard/Cargo.toml | 4 +- .../cpi-guard/tests/test_cpi_guard.rs | 61 ++-- .../cpi-guard/tests/transaction_v1/mod.rs | 94 ------ .../programs/default-account-state/Cargo.toml | 4 +- .../tests/test_default_account_state.rs | 13 +- .../tests/transaction_v1/mod.rs | 94 ------ .../group/anchor-v1/programs/group/Cargo.toml | 4 +- .../programs/group/tests/test_group.rs | 19 +- .../group/tests/transaction_v1/mod.rs | 94 ------ .../programs/immutable-owner/Cargo.toml | 4 +- .../tests/test_immutable_owner.rs | 10 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/interest-bearing/Cargo.toml | 4 +- .../tests/test_interest_bearing.rs | 20 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/memo-transfer/Cargo.toml | 4 +- .../memo-transfer/tests/test_memo_transfer.rs | 53 +++- .../memo-transfer/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/metadata/Cargo.toml | 4 +- .../programs/metadata/tests/test_metadata.rs | 34 ++- .../metadata/tests/transaction_v1/mod.rs | 94 ------ .../programs/mint-close-authority/Cargo.toml | 4 +- .../tests/test_mint_close_authority.rs | 23 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/extension_nft/Cargo.toml | 4 +- .../extension_nft/tests/test_extension_nft.rs | 9 +- .../extension_nft/tests/transaction_v1/mod.rs | 94 ------ .../programs/non-transferable/Cargo.toml | 4 +- .../tests/test_non_transferable.rs | 21 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/permanent-delegate/Cargo.toml | 4 +- .../tests/test_permanent_delegate.rs | 11 +- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-fee/Cargo.toml | 4 +- .../transfer-fee/tests/test_transfer_fee.rs | 68 +++-- .../transfer-fee/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-hook/Cargo.toml | 4 +- .../transfer-hook/tests/test_transfer_hook.rs | 27 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 ------ .../anchor-v1/programs/abl-token/Cargo.toml | 4 +- .../abl-token/tests/test_abl_token.rs | 20 +- .../abl-token/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-hook/Cargo.toml | 4 +- .../tests/test_transfer_hook_counter.rs | 27 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-hook/Cargo.toml | 4 +- .../transfer-hook/tests/test_transfer_hook.rs | 32 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-hook/Cargo.toml | 4 +- .../transfer-hook/tests/test_transfer_hook.rs | 17 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-switch/Cargo.toml | 4 +- .../tests/test_transfer_switch.rs | 48 ++- .../tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-hook/Cargo.toml | 4 +- .../transfer-hook/tests/test_transfer_hook.rs | 27 +- .../transfer-hook/tests/transaction_v1/mod.rs | 94 ------ .../programs/token-minter/Cargo.toml | 4 +- .../token-minter/tests/test_token_minter.rs | 10 +- .../token-minter/tests/transaction_v1/mod.rs | 94 ------ .../programs/transfer-tokens/Cargo.toml | 4 +- .../tests/test_transfer_tokens.rs | 19 +- .../tests/transaction_v1/mod.rs | 94 ------ 180 files changed, 1497 insertions(+), 6169 deletions(-) delete mode 100644 basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs delete mode 100644 basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs delete mode 100644 basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs delete mode 100644 basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs delete mode 100644 basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs delete mode 100644 basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs delete mode 100644 basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs delete mode 100644 basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs delete mode 100644 basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs delete mode 100644 basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs delete mode 100644 basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs delete mode 100644 basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs delete mode 100644 basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs delete mode 100644 basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs delete mode 100644 basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs delete mode 100644 basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs delete mode 100644 basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs delete mode 100644 compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs delete mode 100644 compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs delete mode 100644 compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs delete mode 100644 finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs delete mode 100644 finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs delete mode 100644 finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs delete mode 100644 finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs delete mode 100644 finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs delete mode 100644 finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs delete mode 100644 finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs delete mode 100644 finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs delete mode 100644 finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs delete mode 100644 finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs delete mode 100644 tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs delete mode 100644 tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs delete mode 100644 tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs delete mode 100644 tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs delete mode 100644 tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs delete mode 100644 tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs delete mode 100644 tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ddf2b2e9..ce166974a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,20 +13,21 @@ Programs need no change for it; the client that builds the transaction does. ### Changed -- Every Anchor v1 example's tests send v1 transactions. Each test directory gains a - `transaction_v1` module (identical in all 57 examples) whose - `send_transaction_from_instructions` takes the same arguments and returns the same - error type as `solana-kite`'s, so each test file changes one import and no call - site. Kite's version builds a legacy transaction. The six tests that build a - transaction by hand (to read logs, measure compute units or return the metadata) - use the module's `v1_transaction` instead. -- A v1 transaction's unset limits are zero, not the legacy defaults, so the module - asks for exactly what a legacy transaction gets without ComputeBudget instructions: - 200,000 compute units per instruction up to 1,400,000, and 64 MiB of loaded - account data. The programs under test see the budget they always did. -- The test manifests name `solana-message` 4.2.4 and `solana-transaction` 4.1.5, - where the v1 types live. The three compression examples, which did not use kite, - add it for its error type, so the module is the same file everywhere. +- Every Anchor v1 example's tests send v1 transactions, through `solana-kite` 0.6.0, + whose senders and token, Token Extensions and transfer hook helpers all build v1. + The manifests move from kite 0.5.0 to 0.6.0. +- A v1 transaction's unset limits are zero, not the legacy defaults, so kite 0.6 + sets the compute unit and loaded-accounts data size limits in every transaction it + builds. Its senders and helpers take a new last argument, + `compute_unit_limit: Option`, and every call here passes `None`: kite + simulates the transaction and sends it with the compute units the simulation used. +- The six tests that build a transaction themselves (to read logs, measure compute + units or read a program error code) build it with kite's + `build_transaction_from_instructions` and `get_transaction_config`, with + `MAX_COMPUTE_UNIT_LIMIT` from `solana-compute-budget`, and send it through LiteSVM. + The three compression examples add kite for this. They, `hello-solana`, `options` + and `order-book` no longer use `solana-message` or `solana-transaction` directly, + and drop them from their manifests. ### Added @@ -37,11 +38,6 @@ Programs need no change for it; the client that builds the transaction does. ### Note -- Kite's token helpers (`create_token_mint`, `create_associated_token_account`, - `mint_tokens_to_token_account` and the token extension ones) send their setup - transactions through kite's own legacy builder, and still do. They switch when - kite's `send_transaction_from_instructions` builds v1; the module can then be - deleted and the imports pointed back at kite. - Anchor v2 and Quasar tests still send legacy transactions: `anchor-v2-testing` pins LiteSVM 0.13.1 and `quasar-svm` is on a `solana-message` without the `v1` module. Both predate v1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 520eafc01..4e8a601e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,10 +35,12 @@ Run an example's tests with the command for its framework, from the framework di - **Anchor v2** (in `anchor/`): `anchor test` (runs `cargo test`, per the `[scripts]` table in `Anchor.toml`), with the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.1 --locked`. - **Anchor v1** (in `anchor-v1/`): the same `anchor test`, with the v1 CLI: `avm install 1.2.0 && avm use 1.2.0`. Selecting the wrong CLI is the usual cause of a confusing build failure in these directories. - Anchor v1 tests send v1 transactions (4,096 bytes, resource limits in the message). Import - `send_transaction_from_instructions` from the example's `tests/transaction_v1` module rather than from - `solana-kite`, whose version builds a legacy transaction; the two take the same arguments. A new example - copies that module from any other `anchor-v1/` example unchanged. + Anchor v1 tests send v1 transactions (4,096 bytes, resource limits in the message) through + `solana-kite` 0.6. Its senders take a last `compute_unit_limit: Option`: pass `None` and kite + simulates the transaction and sends it with the compute units the simulation used. A test that needs the + transaction's metadata (logs, compute units, a program error code) builds it with kite's + `build_transaction_from_instructions` and sends it through LiteSVM itself. Kite rejects ComputeBudget + instructions, which do nothing in a v1 transaction. - **Quasar:** `quasar test`. - **Native / Pinocchio:** `cargo test --manifest-path=./program/Cargo.toml` (build first with `cargo build-sbf --manifest-path=./program/Cargo.toml`). diff --git a/README.md b/README.md index 5987cf87b..2b100a9ac 100644 --- a/README.md +++ b/README.md @@ -421,7 +421,7 @@ Start with the [escrow example](./finance/escrow/anchor/), the best first financ ### Do these examples work with Solana transaction v1? -Yes. The v1 format (4,096 byte transactions, live on mainnet since epoch 1035 on 15 September 2026) changes how a client builds a transaction, not what a program sees, so every program here can be called through v1 as is. Every Anchor v1 example's tests send their transactions as v1, through a small `tests/transaction_v1` module, and the [transaction v1 example](./basics/transaction-v1/anchor-v1/) shows the config fields that replace ComputeBudget instructions. The Anchor v2 and Quasar tests still send legacy transactions, because their test harnesses run on virtual machines that predate v1. +Yes. The v1 format (4,096 byte transactions, live on mainnet since epoch 1035 on 15 September 2026) changes how a client builds a transaction, not what a program sees, so every program here can be called through v1 as is. Every Anchor v1 example's tests send their transactions as v1, through [solana-kite](https://crates.io/crates/solana-kite) 0.6, and the [transaction v1 example](./basics/transaction-v1/anchor-v1/) shows the config fields that replace ComputeBudget instructions. The Anchor v2 and Quasar tests still send legacy transactions, because their test harnesses run on virtual machines that predate v1. ### How do I test a Solana program without running a validator? diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml index 75b1400d3..c115b257c 100644 --- a/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/account-data/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs index 7124cc575..26ba480ef 100644 --- a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs +++ b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/test_account_data.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -10,7 +6,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -56,6 +52,7 @@ fn test_create_address_info() { vec![instruction], &[&payer, &address_info_keypair], &payer.pubkey(), + None, ) .unwrap(); diff --git a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/account-data/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml index 1f3d91327..76c29e5dc 100644 --- a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs index bef974c00..1663d0194 100644 --- a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs +++ b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/test_checking_accounts.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_instruction, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -38,6 +34,7 @@ fn test_check_accounts() { vec![create_account_ix], &[&payer, &account_to_change], &payer.pubkey(), + None, ) .unwrap(); @@ -60,6 +57,7 @@ fn test_check_accounts() { vec![check_accounts_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); } diff --git a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/checking-accounts/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/close-account/anchor-v1/programs/close-account/Cargo.toml b/basics/close-account/anchor-v1/programs/close-account/Cargo.toml index c01f6b1df..36342f742 100644 --- a/basics/close-account/anchor-v1/programs/close-account/Cargo.toml +++ b/basics/close-account/anchor-v1/programs/close-account/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs b/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs index a2ded00d3..5e8b11969 100644 --- a/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs +++ b/basics/close-account/anchor-v1/programs/close-account/tests/test_close_account.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -45,7 +41,7 @@ fn test_create_and_close_user() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify account exists and has correct data @@ -66,7 +62,7 @@ fn test_create_and_close_user() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify account is closed diff --git a/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs b/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/close-account/anchor-v1/programs/close-account/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml b/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml index 38ac2f222..ab720090f 100644 --- a/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml +++ b/basics/counter/anchor-v1/programs/counter_anchor/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs b/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs index d3a55fe98..011eb972f 100644 --- a/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs +++ b/basics/counter/anchor-v1/programs/counter_anchor/tests/test_counter.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -10,7 +6,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -57,6 +53,7 @@ fn test_initialize_counter() { vec![instruction], &[&payer, &counter_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -85,6 +82,7 @@ fn test_increment_counter() { vec![init_ix], &[&payer, &counter_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -97,7 +95,8 @@ fn test_increment_counter() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![inc_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![inc_ix], &[&payer], &payer.pubkey(), None) + .unwrap(); let count = fetch_counter(&svm, &counter_keypair.pubkey()); assert_eq!(count, 1, "Expected count to be 1"); @@ -124,6 +123,7 @@ fn test_increment_counter_again() { vec![init_ix], &[&payer, &counter_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -137,8 +137,14 @@ fn test_increment_counter_again() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![inc_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![inc_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); } diff --git a/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs b/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/counter/anchor-v1/programs/counter_anchor/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml b/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml index 009186a50..ef3bf0d7a 100644 --- a/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml +++ b/basics/create-account/anchor-v1/programs/create-system-account/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs b/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs index ee04c17a5..02e7b518b 100644 --- a/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs +++ b/basics/create-account/anchor-v1/programs/create-system-account/tests/test_create_account.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, rent::Rent, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -39,6 +35,7 @@ fn test_create_the_account() { vec![instruction], &[&payer, &new_account], &payer.pubkey(), + None, ) .unwrap(); diff --git a/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs b/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/create-account/anchor-v1/programs/create-system-account/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml b/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml index 27ab0357c..e4b0cd5f6 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml +++ b/basics/cross-program-invocation/anchor-v1/programs/hand/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs index 8cfb39c08..8a4a641c5 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs +++ b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/test_hand.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -12,7 +8,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -77,6 +73,7 @@ fn test_pull_lever_cpi() { vec![init_ix], &[&payer, &power_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -100,7 +97,7 @@ fn test_pull_lever_cpi() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![pull_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![pull_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify power is now on @@ -123,7 +120,7 @@ fn test_pull_lever_cpi() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![pull_ix2], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![pull_ix2], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify power is now off again diff --git a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs b/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/cross-program-invocation/anchor-v1/programs/hand/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml b/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml index 8aacef29b..61defba95 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml +++ b/basics/cross-program-invocation/anchor-v1/programs/lever/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs index 6743a74e0..a3147824b 100644 --- a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs +++ b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/test_lever.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -46,6 +42,7 @@ fn test_initialize_lever() { vec![instruction], &[&payer, &power_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -81,6 +78,7 @@ fn test_switch_power() { vec![init_ix], &[&payer, &power_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -97,7 +95,7 @@ fn test_switch_power() { .to_account_metas(None), ); svm.expire_blockhash(); - send_transaction_from_instructions(&mut svm, vec![switch_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![switch_ix], &[&payer], &payer.pubkey(), None) .unwrap(); assert!( @@ -118,8 +116,14 @@ fn test_switch_power() { .to_account_metas(None), ); svm.expire_blockhash(); - send_transaction_from_instructions(&mut svm, vec![switch_ix2], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![switch_ix2], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); assert!( !read_power_is_on(&svm, &power_keypair.pubkey()), diff --git a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs b/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/cross-program-invocation/anchor-v1/programs/lever/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/favorites/anchor-v1/programs/favorites/Cargo.toml b/basics/favorites/anchor-v1/programs/favorites/Cargo.toml index 6e317208f..03dbb7be5 100644 --- a/basics/favorites/anchor-v1/programs/favorites/Cargo.toml +++ b/basics/favorites/anchor-v1/programs/favorites/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = {version = "1.2.0", features = ["init-if-needed"]} litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs b/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs index 7d13ca290..229ed2f9a 100644 --- a/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs +++ b/basics/favorites/anchor-v1/programs/favorites/tests/test_favorites.rs @@ -1,14 +1,10 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -93,8 +89,14 @@ fn test_set_favorites() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); let data = read_favorites(&svm, &pda); assert_eq!(data.number, 23); @@ -127,8 +129,14 @@ fn test_update_favorites() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Update favorites with new hobbies svm.expire_blockhash(); @@ -152,7 +160,7 @@ fn test_update_favorites() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey(), None) .unwrap(); let data = read_favorites(&svm, &pda); diff --git a/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs b/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/favorites/anchor-v1/programs/favorites/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml b/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml index a52156d70..1aa2e8e1f 100644 --- a/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml +++ b/basics/hello-solana/anchor-v1/programs/hello-solana/Cargo.toml @@ -25,10 +25,10 @@ anchor-lang = "1.2.0" [dev-dependencies] litesvm = "0.16.0" solana-signer = "3.0.0" -solana-kite = "0.5.0" -solana-transaction = "4.1.5" -solana-keypair = "3.0.1" -solana-message = "4.2.4" +solana-kite = "0.6.0" +# The named limit for the one transaction the test builds itself. Same version +# solana-kite 0.6 builds against. +solana-compute-budget = "4.2" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs index 42e8accff..cb6c9b1fb 100644 --- a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs +++ b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/test_hello.rs @@ -1,11 +1,8 @@ -mod transaction_v1; - -use transaction_v1::v1_transaction; - use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, - solana_kite::create_wallet, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, + solana_kite::{build_transaction_from_instructions, create_wallet, get_transaction_config}, solana_signer::Signer, }; @@ -24,9 +21,17 @@ fn test_say_hello() { ); // The program only logs; assert it emitted its greeting rather than merely - // that the transaction landed. The send helper discards the metadata, so - // send through LiteSVM directly to read the logs. - let transaction = v1_transaction(&svm, &[instruction], &[&payer], &payer.pubkey()); + // that the transaction landed. kite's send helper discards the metadata, so + // build the transaction with kite and send it through LiteSVM directly to + // read the logs. + let transaction = build_transaction_from_instructions( + &svm, + &[instruction], + &[&payer], + &payer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); let metadata = svm.send_transaction(transaction).unwrap(); assert!( diff --git a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs b/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/hello-solana/anchor-v1/programs/hello-solana/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml index 81101db5b..6dbcf1cd7 100644 --- a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs index 02fde57c2..f29f15466 100644 --- a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs +++ b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/test_pda_rent_payer.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -44,7 +40,7 @@ fn test_init_rent_vault() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![init_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify the rent vault has the correct balance @@ -79,7 +75,7 @@ fn test_create_new_account_from_rent_vault() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![init_ix], &[&payer], &payer.pubkey(), None) .unwrap(); svm.expire_blockhash(); @@ -101,6 +97,7 @@ fn test_create_new_account_from_rent_vault() { vec![create_ix], &[&payer, &new_account], &payer.pubkey(), + None, ) .unwrap(); diff --git a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/pda-rent-payer/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml b/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml index f401a8e91..7fd431fdb 100644 --- a/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml +++ b/basics/processing-instructions/anchor-v1/programs/processing-instructions/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs index 3b08d0ef8..3e58ed277 100644 --- a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs +++ b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/test_processing_instructions.rs @@ -1,11 +1,7 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -33,7 +29,7 @@ fn test_go_to_park() { .data(), processing_instructions::accounts::ParkAccountConstraints {}.to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![ix_short], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![ix_short], &[&payer], &payer.pubkey(), None) .unwrap(); svm.expire_blockhash(); @@ -48,6 +44,6 @@ fn test_go_to_park() { .data(), processing_instructions::accounts::ParkAccountConstraints {}.to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![ix_tall], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![ix_tall], &[&payer], &payer.pubkey(), None) .unwrap(); } diff --git a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs b/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/processing-instructions/anchor-v1/programs/processing-instructions/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml index 983e64a06..db903f155 100644 --- a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml +++ b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs index ea0cf0ce1..4b5d13cf1 100644 --- a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs +++ b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/test_program_derived_addresses.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, borsh::BorshDeserialize, litesvm::LiteSVM, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -48,7 +44,7 @@ fn test_create_and_increment_page_visits() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify initial state (page_visits = 0) @@ -68,8 +64,14 @@ fn test_create_and_increment_page_visits() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![increment_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![increment_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify page_visits = 1 let account = svm.get_account(&page_visits_pda).expect("PDA should exist"); @@ -91,8 +93,14 @@ fn test_create_and_increment_page_visits() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![increment_ix2], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![increment_ix2], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify page_visits = 2 let account = svm.get_account(&page_visits_pda).expect("PDA should exist"); diff --git a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs b/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/program-derived-addresses/anchor-v1/programs/anchor-program-example/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml b/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml index 1f7f46c82..b565dba33 100644 --- a/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml +++ b/basics/pyth/anchor-v1/programs/pythexample/Cargo.toml @@ -36,9 +36,7 @@ solana-keypair = "3.0.1" solana-account = "4.3.0" borsh = "1.6.1" sha2 = "0.10" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs b/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs index 168d4ea3a..18d5abac9 100644 --- a/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs +++ b/basics/pyth/anchor-v1/programs/pythexample/tests/test_pyth.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{clock::Clock, instruction::Instruction}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, pythexample::MAXIMUM_PRICE_AGE_SECONDS, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -132,8 +128,14 @@ fn test_read_price() { set_clock_to_price_age(&mut svm, MAXIMUM_PRICE_AGE_SECONDS); let instruction = read_price_instruction(price_update_key.pubkey()); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); } #[test] @@ -144,8 +146,13 @@ fn test_read_price_rejects_stale_price() { set_clock_to_price_age(&mut svm, MAXIMUM_PRICE_AGE_SECONDS + 1); let instruction = read_price_instruction(price_update_key.pubkey()); - let result = - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ); assert!(result.is_err(), "a stale price update must be rejected"); } @@ -160,8 +167,13 @@ fn test_read_price_rejects_wrong_owner() { set_clock_to_price_age(&mut svm, 0); let instruction = read_price_instruction(price_update_key.pubkey()); - let result = - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ); assert!( result.is_err(), "a price account not owned by the Pyth Receiver must be rejected" diff --git a/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs b/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/pyth/anchor-v1/programs/pythexample/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml b/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml index b367b3d72..bc5fb752f 100644 --- a/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml +++ b/basics/realloc/anchor-v1/programs/anchor-realloc/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs index 249584db6..b0254a221 100644 --- a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs +++ b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/test_realloc.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -10,7 +6,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -55,6 +51,7 @@ fn test_initialize() { vec![instruction], &[&payer, &message_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -95,6 +92,7 @@ fn test_update_grows() { vec![init_ix], &[&payer, &message_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -113,7 +111,7 @@ fn test_update_grows() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey(), None) .unwrap(); let msg_text = fetch_message(&svm, &message_keypair.pubkey()); @@ -152,6 +150,7 @@ fn test_update_shrinks() { vec![init_ix], &[&payer, &message_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -170,7 +169,7 @@ fn test_update_shrinks() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey(), None) .unwrap(); let msg_text = fetch_message(&svm, &message_keypair.pubkey()); diff --git a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs b/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/realloc/anchor-v1/programs/anchor-realloc/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/rent/anchor-v1/programs/rent-example/Cargo.toml b/basics/rent/anchor-v1/programs/rent-example/Cargo.toml index d0c8773c2..8ed384ec5 100644 --- a/basics/rent/anchor-v1/programs/rent-example/Cargo.toml +++ b/basics/rent/anchor-v1/programs/rent-example/Cargo.toml @@ -27,9 +27,7 @@ litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs b/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs index f68fd3d24..b04c150cf 100644 --- a/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs +++ b/basics/rent/anchor-v1/programs/rent-example/tests/test_rent.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -10,7 +6,7 @@ use { borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -65,6 +61,7 @@ fn test_create_system_account() { vec![instruction], &[&payer, &new_account], &payer.pubkey(), + None, ) .unwrap(); diff --git a/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs b/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/rent/anchor-v1/programs/rent-example/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml b/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml index 8f18f47c3..1fc1db85e 100644 --- a/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml +++ b/basics/repository-layout/anchor-v1/programs/carnival/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs b/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs index 6d382f47d..242ef42bf 100644 --- a/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs +++ b/basics/repository-layout/anchor-v1/programs/carnival/tests/test_carnival.rs @@ -1,11 +1,7 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, litesvm::LiteSVM, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -93,7 +89,8 @@ fn test_go_on_ride_succeeds() { // Scrambler costs 3 tickets and requires 48" min height; Alice has 15 // tickets and is 56" tall. let ix = go_on_ride_ix(&payer, "Alice", 56, 15, "Scrambler"); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } // Insufficient tickets must produce a real error (NotEnoughTickets), not a @@ -103,7 +100,8 @@ fn test_go_on_ride_rejects_insufficient_tickets() { let (mut svm, payer) = setup(); // Tilt-a-Whirl costs 3 tickets; Bob only has 1. let ix = go_on_ride_ix(&payer, "Bob", 49, 1, "Tilt-a-Whirl"); - let result = send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None); assert!(result.is_err(), "ride must reject insufficient tickets"); } @@ -114,7 +112,8 @@ fn test_go_on_ride_rejects_too_short() { // Ferris Wheel requires 55" min height; Jimmy is 36" tall but has plenty // of tickets. let ix = go_on_ride_ix(&payer, "Jimmy", 36, 15, "Ferris Wheel"); - let result = send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None); assert!(result.is_err(), "ride must reject riders below min height"); } @@ -123,7 +122,8 @@ fn test_go_on_ride_rejects_too_short() { fn test_go_on_ride_rejects_unknown_ride() { let (mut svm, payer) = setup(); let ix = go_on_ride_ix(&payer, "Alice", 60, 100, "Quantum Coaster"); - let result = send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None); assert!(result.is_err(), "unknown ride must be rejected"); } @@ -132,7 +132,8 @@ fn test_play_game_succeeds() { let (mut svm, payer) = setup(); // Ring Toss costs 3 tickets; Alice has 15. let ix = play_game_ix(&payer, "Alice", 15, "Ring Toss"); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } #[test] @@ -140,7 +141,8 @@ fn test_play_game_rejects_insufficient_tickets() { let (mut svm, payer) = setup(); // Ring Toss costs 3 tickets; Mary only has 1. let ix = play_game_ix(&payer, "Mary", 1, "Ring Toss"); - let result = send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None); assert!(result.is_err(), "game must reject insufficient tickets"); } @@ -149,7 +151,8 @@ fn test_eat_food_succeeds() { let (mut svm, payer) = setup(); // Dough Boy's costs 1 ticket; Bob has 6. let ix = eat_food_ix(&payer, "Bob", 6, "Dough Boy's"); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } #[test] @@ -157,7 +160,8 @@ fn test_eat_food_rejects_insufficient_tickets() { let (mut svm, payer) = setup(); // Larry's Pizza costs 3 tickets; Mary has 1. let ix = eat_food_ix(&payer, "Mary", 1, "Larry's Pizza"); - let result = send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None); assert!( result.is_err(), "food stand must reject insufficient tickets" diff --git a/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs b/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/repository-layout/anchor-v1/programs/carnival/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/basics/transaction-v1/anchor-v1/README.md b/basics/transaction-v1/anchor-v1/README.md index af92b21df..7b86fe0e9 100644 --- a/basics/transaction-v1/anchor-v1/README.md +++ b/basics/transaction-v1/anchor-v1/README.md @@ -41,7 +41,7 @@ Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/lit anchor test ``` -This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example builds its transactions by hand. Every other Anchor v1 example sends v1 transactions too, through its `tests/transaction_v1` module, which asks for the same limits a legacy transaction gets by default; this one sets each config field itself to show what they do. The suite covers: +This runs `cargo test` as configured in `Anchor.toml`. LiteSVM 0.16.0 is the first release that executes v1 transactions, and this example builds its transactions by hand. Every other Anchor v1 example sends v1 transactions too, through [solana-kite](https://crates.io/crates/solana-kite) 0.6, which sets the compute unit and loaded-accounts data size limits for them; this one sets each config field itself to show what they do. The suite covers: - `stores_a_document_too_large_for_a_legacy_transaction`: a 3,000 byte document in a v1 transaction. The test measures the transaction on the wire (over 1,232 bytes, at most 4,096) and reads the document back from the PDA. - `a_legacy_transaction_still_works_for_a_small_document`: the same instruction with a 500 byte document, sent as a legacy transaction. The program did not change. diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml index c1f60467c..abcc9fb00 100644 --- a/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/Cargo.toml @@ -28,9 +28,8 @@ anchor-lang = "1.2.0" # size from the message's transaction config rather than from ComputeBudget # instructions. # -# The other Anchor v1 examples send v1 transactions through their -# `tests/transaction_v1` module with legacy-equivalent limits. This example -# builds its own, to show each config field. +# The other Anchor v1 examples send v1 transactions through solana-kite 0.6. +# This example builds its own, to show each config field. litesvm = "0.16.0" borsh = "1.6.1" solana-keypair = "3.1" diff --git a/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs b/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs index b83323c06..0ff26c2e7 100644 --- a/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs +++ b/basics/transaction-v1/anchor-v1/programs/transaction-v1/tests/test_transaction_v1.rs @@ -84,7 +84,7 @@ fn store_document(program_id: &Pubkey, payer: &Pubkey, document: &[u8]) -> (Inst /// Compile a v1 message and sign it. `try_compile_with_config` is the v1 /// counterpart of `Message::new_with_blockhash`; the config rides in the /// message header, so there is no ComputeBudget instruction to append. -fn v1_transaction( +fn build_v1_transaction( svm: &LiteSVM, payer: &Keypair, instructions: &[Instruction], @@ -105,10 +105,16 @@ fn wire_size(transaction: &VersionedTransaction) -> usize { wincode::serialize(transaction).unwrap().len() } +/// The largest prime below 256: the byte pattern repeats every 251 bytes, a +/// period no power-of-two offset lines up with. +const SAMPLE_DOCUMENT_PATTERN_LENGTH: usize = 251; + /// A document with a recognizable pattern, so a partial or shifted copy /// would not pass the equality check. fn sample_document(length: usize) -> Vec { - (0..length).map(|i| (i % 251) as u8).collect() + (0..length) + .map(|index| (index % SAMPLE_DOCUMENT_PATTERN_LENGTH) as u8) + .collect() } fn stored_document(svm: &LiteSVM, document_address: &Pubkey) -> Vec { @@ -122,7 +128,7 @@ fn stores_a_document_too_large_for_a_legacy_transaction() { let document = sample_document(3_000); let (instruction, document_address) = store_document(&program_id, &payer.pubkey(), &document); - let transaction = v1_transaction(&svm, &payer, &[instruction], transaction_config()); + let transaction = build_v1_transaction(&svm, &payer, &[instruction], transaction_config()); // Over the legacy limit, under the v1 one. LiteSVM does not enforce either // (a validator's packet layer does), so the test checks the size itself. @@ -175,7 +181,7 @@ fn the_transaction_config_replaces_compute_budget_instructions() { // A v1 priority fee is a total in lamports, not a price per compute unit. let priority_fee_lamports = 10_000; let config = transaction_config().with_priority_fee(priority_fee_lamports); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let transaction = build_v1_transaction(&svm, &payer, &[instruction], config); // v1 moved the signatures to the end, so the first byte on the wire is the // message version rather than a signature count. 0x81 means v1. @@ -200,7 +206,7 @@ fn unset_config_fields_mean_zero_not_the_default() { // No loaded-accounts data size limit: the transaction fails before any // program runs, because zero bytes of account data may be loaded. - let transaction = v1_transaction( + let transaction = build_v1_transaction( &svm, &payer, std::slice::from_ref(&instruction), @@ -216,7 +222,7 @@ fn unset_config_fields_mean_zero_not_the_default() { // budget of zero and fails on its first instruction. let config = v1::TransactionConfig::empty() .with_loaded_accounts_data_size_limit(LOADED_ACCOUNTS_DATA_SIZE_LIMIT); - let transaction = v1_transaction(&svm, &payer, &[instruction], config); + let transaction = build_v1_transaction(&svm, &payer, &[instruction], config); let failure = svm.send_transaction(transaction).unwrap_err(); assert!(matches!( failure.err, diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml b/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml index fd99c51a8..60e155c37 100644 --- a/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml +++ b/basics/transfer-sol/anchor-v1/programs/transfer-sol/Cargo.toml @@ -26,9 +26,7 @@ anchor-lang = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs index 4d9af4105..fab096576 100644 --- a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs +++ b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/test_transfer_sol.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -39,8 +35,14 @@ fn test_transfer_sol_with_cpi() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); let recipient_balance = svm.get_balance(&recipient.pubkey()).unwrap(); assert_eq!(recipient_balance, LAMPORTS_PER_SOL); @@ -68,6 +70,7 @@ fn test_transfer_sol_with_program() { vec![create_account_ix], &[&payer, &payer_account], &payer.pubkey(), + None, ) .unwrap(); @@ -88,8 +91,14 @@ fn test_transfer_sol_with_program() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); let recipient_balance = svm.get_balance(&recipient.pubkey()).unwrap(); assert_eq!(recipient_balance, LAMPORTS_PER_SOL); @@ -117,6 +126,7 @@ fn test_transfer_sol_with_program_rejects_insufficient_funds() { vec![create_account_ix], &[&payer, &program_owned_account], &payer.pubkey(), + None, ) .unwrap(); @@ -137,8 +147,13 @@ fn test_transfer_sol_with_program_rejects_insufficient_funds() { .to_account_metas(None), ); - let result = - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ); assert!(result.is_err(), "overdrawing the payer must fail"); // Balances are untouched. diff --git a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs b/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/basics/transfer-sol/anchor-v1/programs/transfer-sol/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml index 27bc2c05c..8ad72463d 100644 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml @@ -31,13 +31,16 @@ litesvm = "0.16.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" -solana-transaction = "4.1.5" solana-account = "4.3.0" solana-native-token = "3.0.0" solana-signer = "3.0.0" -solana-message = "4.2.4" +# Builds each v1 transaction the tests send. They send it through LiteSVM +# themselves to read the program error a failure carries. +solana-kite = "0.6.0" +# The named limit those transactions get. Same version solana-kite 0.6 builds +# against. +solana-compute-budget = "4.2" solana-keccak-hasher = "3.0.0" -solana-kite = "0.5.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs index e5a93b83e..117db8b8d 100644 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/test_burn.rs @@ -12,16 +12,14 @@ //! 6. Call our program's `burn_cnft`, signed by `leaf_owner`, and assert the //! transaction succeeds and a second burn fails (leaf already zeroed). -mod transaction_v1; - -use transaction_v1::v1_transaction; - use { borsh::BorshSerialize, litesvm::LiteSVM, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, + solana_kite::{build_transaction_from_instructions, get_transaction_config}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, }; @@ -243,12 +241,21 @@ fn read_current_root(data: &[u8]) -> [u8; 32] { fn send( svm: &mut LiteSVM, - ixs: Vec, + instructions: Vec, payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) + let transaction = build_transaction_from_instructions( + svm, + &instructions, + signers, + &payer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(Box::new) } #[test] diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs b/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml index 042a0eeb0..18183ff9a 100644 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml @@ -34,10 +34,13 @@ litesvm = "0.16.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" -solana-transaction = "4.1.5" solana-account = "4.3.0" solana-native-token = "3.0.0" solana-signer = "3.0.0" -solana-message = "4.2.4" +# Builds each v1 transaction the tests send. They send it through LiteSVM +# themselves to read the program error a failure carries. +solana-kite = "0.6.0" +# The named limit those transactions get. Same version solana-kite 0.6 builds +# against. +solana-compute-budget = "4.2" solana-keccak-hasher = "3.0.0" -solana-kite = "0.5.0" diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs index f04f92cff..e391ec7ed 100644 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/test_vault.rs @@ -25,17 +25,15 @@ //! proof accounts fails with `VaultError::ProofLengthMismatch` instead //! of panicking inside `split_at` -mod transaction_v1; - -use transaction_v1::v1_transaction; - use { borsh::BorshSerialize, cnft_vault::error::VaultError, litesvm::LiteSVM, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, + solana_kite::{build_transaction_from_instructions, get_transaction_config}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, }; @@ -253,12 +251,21 @@ fn read_current_root(data: &[u8]) -> [u8; 32] { fn send( svm: &mut LiteSVM, - ixs: Vec, + instructions: Vec, payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) + let transaction = build_transaction_from_instructions( + svm, + &instructions, + signers, + &payer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(Box::new) } /// Assert a failed transaction carries the given program error. diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs b/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml index a8c4a4e0c..092cae680 100644 --- a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml @@ -36,10 +36,13 @@ litesvm = "0.16.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" -solana-transaction = "4.1.5" solana-account = "4.3.0" solana-native-token = "3.0.0" solana-signer = "3.0.0" -solana-message = "4.2.4" +# Builds each v1 transaction the tests send. They send it through LiteSVM +# themselves to read the program error a failure carries. +solana-kite = "0.6.0" +# The named limit those transactions get. Same version solana-kite 0.6 builds +# against. +solana-compute-budget = "4.2" solana-keccak-hasher = "3.0.0" -solana-kite = "0.5.0" diff --git a/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs b/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs index 33dddf476..fd9351277 100644 --- a/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs +++ b/compression/cutils/anchor-v1/programs/cutils/tests/test_cutils.rs @@ -25,16 +25,14 @@ //! asserting success. A second `verify` with a tampered data_hash must //! fail. -mod transaction_v1; - -use transaction_v1::v1_transaction; - use { borsh::BorshSerialize, litesvm::LiteSVM, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, + solana_kite::{build_transaction_from_instructions, get_transaction_config}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, }; @@ -282,12 +280,21 @@ fn get_asset_id(tree: &Pubkey, nonce: u64) -> Pubkey { fn send( svm: &mut LiteSVM, - ixs: Vec, + instructions: Vec, payer: &Keypair, signers: &[&Keypair], ) -> Result<(), Box> { - let tx = v1_transaction(svm, &ixs, signers, &payer.pubkey()); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) + let transaction = build_transaction_from_instructions( + svm, + &instructions, + signers, + &payer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); + svm.send_transaction(transaction) + .map(|_| ()) + .map_err(Box::new) } fn metadata_pda(mint: &Pubkey) -> Pubkey { diff --git a/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs b/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/compression/cutils/anchor-v1/programs/cutils/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml b/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml index c97dcfcb6..7ba08649c 100644 --- a/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml +++ b/finance/betting-market/anchor-v1/programs/betting-market/Cargo.toml @@ -34,10 +34,8 @@ spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs b/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs index a98ebd8be..46390cd1d 100644 --- a/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs +++ b/finance/betting-market/anchor-v1/programs/betting-market/tests/test_betting_market.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ prelude::Clock, @@ -15,7 +11,8 @@ use { solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, - get_token_account_balance, mint_tokens_to_token_account, SolanaKiteError, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, SolanaKiteError, }, solana_signer::Signer, }; @@ -91,11 +88,12 @@ fn setup() -> Market { .unwrap(); let admin = create_wallet(&mut svm, 100_000_000_000).unwrap(); - let mint = create_token_mint(&mut svm, &admin, DECIMALS, None).unwrap(); + let mint = create_token_mint(&mut svm, &admin, DECIMALS, None, None).unwrap(); let fee_recipient = create_wallet(&mut svm, 10_000_000_000).unwrap(); let fee_recipient_ata = - create_associated_token_account(&mut svm, &fee_recipient.pubkey(), &mint, &admin).unwrap(); + create_associated_token_account(&mut svm, &fee_recipient.pubkey(), &mint, &admin, None) + .unwrap(); Market { svm, @@ -148,10 +146,18 @@ fn create_bettor(market: &mut Market, amount: u64) -> (Keypair, Pubkey) { &bettor.pubkey(), &market.mint, &market.admin, + None, + ) + .unwrap(); + mint_tokens_to_token_account( + &mut market.svm, + &market.mint, + &ata, + amount, + &market.admin, + None, ) .unwrap(); - mint_tokens_to_token_account(&mut market.svm, &market.mint, &ata, amount, &market.admin) - .unwrap(); (bettor, ata) } @@ -457,6 +463,7 @@ fn transaction_fee(market: &mut Market) -> u64 { vec![system_instruction::transfer(&admin, &admin, 0)], &[&market.admin], &admin, + None, ) .unwrap(); before - get_sol_balance(&market.svm, &admin) @@ -488,6 +495,7 @@ fn close_outcomes_and_event(market: &mut Market, event_id: u64, outcome_count: u vec![close_outcome_ix(admin, event_id, index)], &[&market.admin], &admin, + None, ) .unwrap(); assert!( @@ -517,6 +525,7 @@ fn close_outcomes_and_event(market: &mut Market, event_id: u64, outcome_count: u )], &[&market.admin], &admin, + None, ) .unwrap(); assert!(!account_is_open(market, &event)); @@ -549,6 +558,7 @@ fn init_config(market: &mut Market) { vec![initialize_config_ix(admin, mint, fee_recipient)], &[&market.admin], &admin, + None, ) .unwrap(); } @@ -578,6 +588,7 @@ fn test_full_lifecycle() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -593,6 +604,7 @@ fn test_full_lifecycle() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -607,6 +619,7 @@ fn test_full_lifecycle() { )], &[&bob], &bob.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -621,6 +634,7 @@ fn test_full_lifecycle() { )], &[&carol], &carol.pubkey(), + None, ) .unwrap(); @@ -654,6 +668,7 @@ fn test_full_lifecycle() { )], &[&market.admin], &admin, + None, ) .unwrap(); assert_eq!( @@ -673,6 +688,7 @@ fn test_full_lifecycle() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -686,6 +702,7 @@ fn test_full_lifecycle() { )], &[&bob], &bob.pubkey(), + None, ) .unwrap(); @@ -723,6 +740,7 @@ fn test_full_lifecycle() { )], &[&carol], &carol.pubkey(), + None, ); assert_fails_with(carol_claim, BettingError::NothingToClaim); @@ -734,6 +752,7 @@ fn test_full_lifecycle() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ) .unwrap(); assert!(!account_is_open(&market, &carol_bet)); @@ -758,6 +777,7 @@ fn test_only_admin_can_initialize_event() { )], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(result, BettingError::Unauthorized); @@ -771,6 +791,7 @@ fn test_only_admin_can_initialize_event() { ], &[&market.admin], &admin, + None, ) .unwrap(); let unauthorized_outcome = send_transaction_from_instructions( @@ -778,6 +799,7 @@ fn test_only_admin_can_initialize_event() { vec![add_outcome_ix(mallory.pubkey(), event_id, 2, "Late entry")], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(unauthorized_outcome, BettingError::Unauthorized); let unauthorized_open = send_transaction_from_instructions( @@ -785,6 +807,7 @@ fn test_only_admin_can_initialize_event() { vec![open_betting_ix(mallory.pubkey(), event_id)], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(unauthorized_open, BettingError::Unauthorized); } @@ -813,6 +836,7 @@ fn test_only_admin_can_settle_or_cancel_event() { vec![cancel_event_ix(mallory.pubkey(), event_id)], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(unauthorized_cancel, BettingError::Unauthorized); @@ -830,6 +854,7 @@ fn test_only_admin_can_settle_or_cancel_event() { )], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(unauthorized_settle, BettingError::Unauthorized); assert!(read_event(&market, event_id).status == EventStatus::Open); @@ -865,6 +890,7 @@ fn test_cannot_bet_after_settle() { ], &[&market.admin], &admin, + None, ) .unwrap(); send_transaction_from_instructions( @@ -879,6 +905,7 @@ fn test_cannot_bet_after_settle() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); // Betting has closed, so the event can be settled. @@ -895,6 +922,7 @@ fn test_cannot_bet_after_settle() { )], &[&market.admin], &admin, + None, ) .unwrap(); @@ -910,6 +938,7 @@ fn test_cannot_bet_after_settle() { )], &[&bob], &bob.pubkey(), + None, ); assert_fails_with(late_bet, BettingError::EventNotOpen); } @@ -936,6 +965,7 @@ fn test_double_claim_fails() { ], &[&market.admin], &admin, + None, ) .unwrap(); send_transaction_from_instructions( @@ -950,6 +980,7 @@ fn test_double_claim_fails() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -964,6 +995,7 @@ fn test_double_claim_fails() { )], &[&carol], &carol.pubkey(), + None, ) .unwrap(); // Betting has closed, so the event can be settled. @@ -980,6 +1012,7 @@ fn test_double_claim_fails() { )], &[&market.admin], &admin, + None, ) .unwrap(); @@ -994,6 +1027,7 @@ fn test_double_claim_fails() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); // A fresh blockhash so the second claim is a distinct transaction. @@ -1009,6 +1043,7 @@ fn test_double_claim_fails() { )], &[&alice], &alice.pubkey(), + None, ); // The first claim closed the Bet account (`close = bettor`), so the second // transaction passes an address with no lamports and no data. Anchor @@ -1038,6 +1073,7 @@ fn test_settle_outcome_without_bets_fails() { ], &[&market.admin], &admin, + None, ) .unwrap(); // Everyone bets Horse A; Horse B has no bets. @@ -1053,6 +1089,7 @@ fn test_settle_outcome_without_bets_fails() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); @@ -1070,6 +1107,7 @@ fn test_settle_outcome_without_bets_fails() { )], &[&market.admin], &admin, + None, ); assert_fails_with(result, BettingError::OutcomeHasNoBets); } @@ -1094,6 +1132,7 @@ fn test_cancel_and_refund() { ], &[&market.admin], &admin, + None, ) .unwrap(); send_transaction_from_instructions( @@ -1108,6 +1147,7 @@ fn test_cancel_and_refund() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -1122,6 +1162,7 @@ fn test_cancel_and_refund() { )], &[&carol], &carol.pubkey(), + None, ) .unwrap(); @@ -1130,6 +1171,7 @@ fn test_cancel_and_refund() { vec![cancel_event_ix(admin, event_id)], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1147,6 +1189,7 @@ fn test_cancel_and_refund() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); @@ -1159,6 +1202,7 @@ fn test_cancel_and_refund() { vec![close_outcome_ix(admin, event_id, 0)], &[&market.admin], &admin, + None, ); assert_fails_with(outcome_still_in_use, BettingError::BetsStillOpen); assert_eq!(read_event(&market, event_id).open_bets, 1); @@ -1174,6 +1218,7 @@ fn test_cancel_and_refund() { )], &[&carol], &carol.pubkey(), + None, ) .unwrap(); @@ -1222,6 +1267,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { ], &[&market.admin], &admin, + None, ) .unwrap(); send_transaction_from_instructions( @@ -1236,6 +1282,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -1250,6 +1297,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { )], &[&carol], &carol.pubkey(), + None, ) .unwrap(); @@ -1259,6 +1307,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ); assert_fails_with(premature_close, BettingError::EventNotSettled); @@ -1276,6 +1325,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { )], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1285,6 +1335,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { vec![close_losing_bet_ix(&alice.pubkey(), event_id, 0)], &[&alice], &alice.pubkey(), + None, ); assert_fails_with(winner_close, BettingError::BetWon); let alice_bet = bet_pda(&outcome_pda(&event_pda(event_id), 0), &alice.pubkey()); @@ -1298,6 +1349,7 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ) .unwrap(); let carol_bet = bet_pda(&outcome_pda(&event_pda(event_id), 1), &carol.pubkey()); @@ -1330,6 +1382,7 @@ fn test_no_cap_on_open_bets_per_wallet() { )], &[&market.admin], &admin, + None, ) .unwrap(); for index in 0..OUTCOME_COUNT { @@ -1343,6 +1396,7 @@ fn test_no_cap_on_open_bets_per_wallet() { )], &[&market.admin], &admin, + None, ) .unwrap(); } @@ -1351,6 +1405,7 @@ fn test_no_cap_on_open_bets_per_wallet() { vec![open_betting_ix(admin, wide_event_id)], &[&market.admin], &admin, + None, ) .unwrap(); send_transaction_from_instructions( @@ -1363,6 +1418,7 @@ fn test_no_cap_on_open_bets_per_wallet() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1379,6 +1435,7 @@ fn test_no_cap_on_open_bets_per_wallet() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); } @@ -1394,6 +1451,7 @@ fn test_no_cap_on_open_bets_per_wallet() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); @@ -1435,6 +1493,7 @@ fn test_outcomes_lock_when_betting_opens() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1451,6 +1510,7 @@ fn test_outcomes_lock_when_betting_opens() { )], &[&alice], &alice.pubkey(), + None, ); assert_fails_with(early_bet, BettingError::EventNotOpen); @@ -1463,6 +1523,7 @@ fn test_outcomes_lock_when_betting_opens() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1472,6 +1533,7 @@ fn test_outcomes_lock_when_betting_opens() { vec![add_outcome_ix(admin, event_id, 2, "Late entry")], &[&market.admin], &admin, + None, ); assert_fails_with(late_outcome, BettingError::EventNotDraft); @@ -1481,6 +1543,7 @@ fn test_outcomes_lock_when_betting_opens() { vec![open_betting_ix(admin, event_id)], &[&market.admin], &admin, + None, ); assert_fails_with(reopen, BettingError::EventNotDraft); } @@ -1501,6 +1564,7 @@ fn test_open_betting_needs_two_outcomes() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1509,6 +1573,7 @@ fn test_open_betting_needs_two_outcomes() { vec![open_betting_ix(admin, event_id)], &[&market.admin], &admin, + None, ); assert_fails_with(result, BettingError::NotEnoughOutcomes); @@ -1518,6 +1583,7 @@ fn test_open_betting_needs_two_outcomes() { vec![add_outcome_ix(admin, event_id, 1, "A second horse")], &[&market.admin], &admin, + None, ) .unwrap(); let mallory = create_wallet(&mut market.svm, 10_000_000_000).unwrap(); @@ -1526,6 +1592,7 @@ fn test_open_betting_needs_two_outcomes() { vec![open_betting_ix(mallory.pubkey(), event_id)], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(unauthorized, BettingError::Unauthorized); } @@ -1555,6 +1622,7 @@ fn test_betting_closes_at_close_time() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1572,6 +1640,7 @@ fn test_betting_closes_at_close_time() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); @@ -1588,6 +1657,7 @@ fn test_betting_closes_at_close_time() { )], &[&market.admin], &admin, + None, ); assert_fails_with(early_settle, BettingError::BettingStillOpen); @@ -1605,6 +1675,7 @@ fn test_betting_closes_at_close_time() { )], &[&carol], &carol.pubkey(), + None, ); assert_fails_with(late_bet, BettingError::BettingClosed); @@ -1620,6 +1691,7 @@ fn test_betting_closes_at_close_time() { )], &[&market.admin], &admin, + None, ) .unwrap(); } @@ -1642,6 +1714,7 @@ fn test_close_time_must_be_in_the_future() { )], &[&market.admin], &admin, + None, ); assert_fails_with(result, BettingError::CloseTimeInPast); } @@ -1661,6 +1734,7 @@ fn open_yes_no_market(market: &mut Market, event_id: u64, description: &str) { ], &[&market.admin], &admin, + None, ) .unwrap(); } @@ -1686,6 +1760,7 @@ fn place_bet( )], &[bettor], &bettor.pubkey(), + None, ) .unwrap(); } @@ -1709,6 +1784,7 @@ fn settle(market: &mut Market, event_id: u64, winning_outcome_index: u8) { )], &[&market.admin], &admin, + None, ) .unwrap(); } @@ -1757,6 +1833,7 @@ fn test_close_event_pays_dust_to_fee_recipient_and_returns_rent() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ) .unwrap(); assert_eq!(read_event(&market, event_id).open_bets, 2); @@ -1772,6 +1849,7 @@ fn test_close_event_pays_dust_to_fee_recipient_and_returns_rent() { )], &[winner], &winner.pubkey(), + None, ) .unwrap(); } @@ -1801,6 +1879,7 @@ fn test_close_event_pays_dust_to_fee_recipient_and_returns_rent() { )], &[&market.admin], &admin, + None, ); assert_fails_with(outcomes_still_open, BettingError::OutcomesStillOpen); @@ -1846,6 +1925,7 @@ fn test_close_event_refused_while_a_bet_is_open() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); assert_eq!(read_event(&market, event_id).open_bets, 1); @@ -1861,6 +1941,7 @@ fn test_close_event_refused_while_a_bet_is_open() { )], &[&market.admin], &admin, + None, ); assert_fails_with(event_close, BettingError::BetsStillOpen); let outcome_close = send_transaction_from_instructions( @@ -1868,6 +1949,7 @@ fn test_close_event_refused_while_a_bet_is_open() { vec![close_outcome_ix(admin, event_id, 1)], &[&market.admin], &admin, + None, ); assert_fails_with(outcome_close, BettingError::BetsStillOpen); @@ -1877,6 +1959,7 @@ fn test_close_event_refused_while_a_bet_is_open() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ) .unwrap(); close_outcomes_and_event(&mut market, event_id, 2); @@ -1903,6 +1986,7 @@ fn test_close_event_refused_while_event_is_open() { ], &[&market.admin], &admin, + None, ) .unwrap(); @@ -1918,6 +2002,7 @@ fn test_close_event_refused_while_event_is_open() { )], &[&market.admin], &admin, + None, ); assert_fails_with(draft_close, BettingError::EventNotFinished); let draft_outcome_close = send_transaction_from_instructions( @@ -1925,6 +2010,7 @@ fn test_close_event_refused_while_event_is_open() { vec![close_outcome_ix(admin, event_id, 0)], &[&market.admin], &admin, + None, ); assert_fails_with(draft_outcome_close, BettingError::EventNotFinished); @@ -1935,6 +2021,7 @@ fn test_close_event_refused_while_event_is_open() { vec![open_betting_ix(admin, event_id)], &[&market.admin], &admin, + None, ) .unwrap(); market.svm.expire_blockhash(); @@ -1949,6 +2036,7 @@ fn test_close_event_refused_while_event_is_open() { )], &[&market.admin], &admin, + None, ); assert_fails_with(open_close, BettingError::EventNotFinished); @@ -1958,6 +2046,7 @@ fn test_close_event_refused_while_event_is_open() { vec![cancel_event_ix(admin, event_id)], &[&market.admin], &admin, + None, ) .unwrap(); let vault_balance_at_close = close_outcomes_and_event(&mut market, event_id, 2); @@ -1990,6 +2079,7 @@ fn test_only_admin_can_close_outcomes_and_event() { )], &[&alice], &alice.pubkey(), + None, ) .unwrap(); send_transaction_from_instructions( @@ -1997,6 +2087,7 @@ fn test_only_admin_can_close_outcomes_and_event() { vec![close_losing_bet_ix(&carol.pubkey(), event_id, 1)], &[&carol], &carol.pubkey(), + None, ) .unwrap(); @@ -2008,6 +2099,7 @@ fn test_only_admin_can_close_outcomes_and_event() { vec![close_outcome_ix(mallory.pubkey(), event_id, 0)], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(outcome_close, BettingError::Unauthorized); let event_close = send_transaction_from_instructions( @@ -2021,6 +2113,7 @@ fn test_only_admin_can_close_outcomes_and_event() { )], &[&mallory], &mallory.pubkey(), + None, ); assert_fails_with(event_close, BettingError::Unauthorized); let event = event_pda(event_id); diff --git a/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs b/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/betting-market/anchor-v1/programs/betting-market/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/escrow/anchor-v1/programs/escrow/Cargo.toml b/finance/escrow/anchor-v1/programs/escrow/Cargo.toml index 3026b5517..224e255a1 100644 --- a/finance/escrow/anchor-v1/programs/escrow/Cargo.toml +++ b/finance/escrow/anchor-v1/programs/escrow/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs b/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs index e0c011ce6..e18e22486 100644 --- a/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs +++ b/finance/escrow/anchor-v1/programs/escrow/tests/test_escrow.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ error::ErrorCode, @@ -13,6 +9,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -90,23 +87,24 @@ fn full_setup() -> EscrowSetup { // Token A is USDC and token B is TSLAx; the payer is the mint authority // of both. - let mint_a = create_token_mint(&mut svm, &payer, USDC_DECIMALS, None).unwrap(); - let mint_b = create_token_mint(&mut svm, &payer, TSLAX_DECIMALS, None).unwrap(); + let mint_a = create_token_mint(&mut svm, &payer, USDC_DECIMALS, None, None).unwrap(); + let mint_b = create_token_mint(&mut svm, &payer, TSLAX_DECIMALS, None, None).unwrap(); // Create ATAs let alice_ata_a = - create_associated_token_account(&mut svm, &alice.pubkey(), &mint_a, &payer).unwrap(); + create_associated_token_account(&mut svm, &alice.pubkey(), &mint_a, &payer, None).unwrap(); let alice_ata_b = - create_associated_token_account(&mut svm, &alice.pubkey(), &mint_b, &payer).unwrap(); + create_associated_token_account(&mut svm, &alice.pubkey(), &mint_b, &payer, None).unwrap(); let bob_ata_a = - create_associated_token_account(&mut svm, &bob.pubkey(), &mint_a, &payer).unwrap(); + create_associated_token_account(&mut svm, &bob.pubkey(), &mint_a, &payer, None).unwrap(); let bob_ata_b = - create_associated_token_account(&mut svm, &bob.pubkey(), &mint_b, &payer).unwrap(); + create_associated_token_account(&mut svm, &bob.pubkey(), &mint_b, &payer, None).unwrap(); // Both hold 1,000 USDC, and Bob holds the TSLAx. - mint_tokens_to_token_account(&mut svm, &mint_a, &alice_ata_a, ALICE_USDC, &payer).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_a, &bob_ata_a, BOB_USDC, &payer).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_b, &bob_ata_b, BOB_TSLAX, &payer).unwrap(); + mint_tokens_to_token_account(&mut svm, &mint_a, &alice_ata_a, ALICE_USDC, &payer, None) + .unwrap(); + mint_tokens_to_token_account(&mut svm, &mint_a, &bob_ata_a, BOB_USDC, &payer, None).unwrap(); + mint_tokens_to_token_account(&mut svm, &mint_b, &bob_ata_b, BOB_TSLAX, &payer, None).unwrap(); EscrowSetup { svm, @@ -172,6 +170,7 @@ fn test_make_offer() { vec![make_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); @@ -246,6 +245,7 @@ fn test_take_offer() { vec![make_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); @@ -285,6 +285,7 @@ fn test_take_offer() { vec![take_offer_ix], &[&es.payer, &es.bob], &es.payer.pubkey(), + None, ) .unwrap(); @@ -386,6 +387,7 @@ fn test_cancel_offer() { vec![make_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); @@ -415,6 +417,7 @@ fn test_cancel_offer() { vec![cancel_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); @@ -486,6 +489,7 @@ fn test_cancel_offer_rejects_non_maker() { vec![make_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); @@ -512,6 +516,7 @@ fn test_cancel_offer_rejects_non_maker() { vec![cancel_offer_ix], &[&es.payer, &es.bob], &es.payer.pubkey(), + None, ); // Bob passes himself as the maker, so the offer's seeds no longer derive // its address: Anchor's seeds check fails before `has_one = maker` runs. @@ -591,6 +596,7 @@ fn try_make_offer( vec![make_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) } @@ -721,6 +727,7 @@ fn cancel_offer(es: &mut EscrowSetup, offer_id: u64) { vec![cancel_offer_ix], &[&es.payer, &es.alice], &es.payer.pubkey(), + None, ) .unwrap(); } @@ -783,6 +790,7 @@ fn assert_switched_offer_refused(switched_a_offered: u64, switched_b_wanted: u64 vec![take_offer_ix], &[&es.payer, &es.bob], &es.payer.pubkey(), + None, ); assert_fails_with(result, escrow::error::EscrowError::OfferTermsChanged); diff --git a/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs b/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/escrow/anchor-v1/programs/escrow/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml b/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml index e5ed3056f..077406a68 100644 --- a/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml +++ b/finance/fundraiser/anchor-v1/programs/fundraiser/Cargo.toml @@ -27,7 +27,7 @@ anchor-spl = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" # solana-kite depends on these SPL program crates without "no-entrypoint", # so the host test binary would link their `entrypoint` symbols alongside @@ -35,8 +35,6 @@ borsh = "1.6.1" # "no-entrypoint" here removes theirs via feature unification. spl-token = { version = "9.0.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] } -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs index df903e5c2..6bbd62873 100644 --- a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/test_fundraiser.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{clock::Clock, instruction::Instruction, pubkey::Pubkey, system_program}, @@ -14,6 +10,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -114,7 +111,7 @@ fn full_setup() -> FundraiserSetup { let maker = create_wallet(&mut svm, 10_000_000_000).unwrap(); // The payer is the mint authority. - let mint = create_token_mint(&mut svm, &payer, MINT_DECIMALS, None).unwrap(); + let mint = create_token_mint(&mut svm, &payer, MINT_DECIMALS, None, None).unwrap(); let (fundraiser_pda, _bump) = Pubkey::find_program_address(&[b"fundraiser", maker.pubkey().as_ref()], &program_id); @@ -153,6 +150,7 @@ fn initialize_fundraiser(setup: &mut FundraiserSetup, amount: u64, duration: u16 vec![initialize_instruction], &[&setup.maker], &setup.maker.pubkey(), + None, ) .unwrap(); } @@ -167,6 +165,7 @@ fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Pubkey, P &contributor.pubkey(), &setup.mint, &setup.payer, + None, ) .unwrap(); @@ -176,6 +175,7 @@ fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Pubkey, P &contributor_ata, CONTRIBUTOR_STARTING_BALANCE, &setup.payer, + None, ) .unwrap(); @@ -320,6 +320,7 @@ fn contribute( vec![contribute_instruction], &[&contributor.keypair], &contributor.keypair.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -363,6 +364,7 @@ fn refund( vec![refund_instruction], &[fee_payer], &fee_payer.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -378,6 +380,7 @@ fn claim(setup: &mut FundraiserSetup) -> Result { vec![check_instruction], &[&maker], &maker.pubkey(), + None, ) .map(|_| maker_ata) .map_err(|error| format!("{error:?}")) @@ -400,6 +403,7 @@ fn close_contribution( vec![close_instruction], &[fee_payer], &fee_payer.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -415,6 +419,7 @@ fn close_fundraiser(setup: &mut FundraiserSetup) -> Result<(), String> { vec![close_instruction], &[&maker], &maker.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -512,6 +517,7 @@ fn test_initialize_below_minimum_target_fails() { vec![initialize_instruction], &[&setup.maker], &setup.maker.pubkey(), + None, ) .map_err(|error| format!("{error:?}")); assert_error(result, FundraiserError::InvalidAmount); @@ -759,6 +765,7 @@ fn test_second_claim_fails() { &setup.vault, ONE_TOKEN, &setup.payer, + None, ) .unwrap(); setup.svm.expire_blockhash(); @@ -803,6 +810,7 @@ fn test_check_contributions_ignores_direct_vault_donations() { &setup.vault, AMOUNT_TO_RAISE, &setup.payer, + None, ) .unwrap(); @@ -1032,6 +1040,7 @@ fn test_reinitialize_with_open_contributions_fails() { vec![initialize_instruction], &[&setup.maker], &setup.maker.pubkey(), + None, ) .map_err(|error| format!("{error:?}")); // `init` asks the System Program to allocate the fundraiser's address, @@ -1135,6 +1144,7 @@ fn test_close_fundraiser_sweeps_direct_donations_to_maker() { &setup.vault, donation, &setup.payer, + None, ) .unwrap(); diff --git a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs b/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/fundraiser/anchor-v1/programs/fundraiser/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/lending/anchor-v1/programs/lending/Cargo.toml b/finance/lending/anchor-v1/programs/lending/Cargo.toml index 23ee2e53f..ea9c3ccce 100644 --- a/finance/lending/anchor-v1/programs/lending/Cargo.toml +++ b/finance/lending/anchor-v1/programs/lending/Cargo.toml @@ -32,10 +32,8 @@ solana-sysvar = "3" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs index 5a6e4438c..f94e7221d 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs @@ -18,14 +18,10 @@ use litesvm::LiteSVM; use solana_keypair::Keypair; use solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, - get_token_account_balance, mint_tokens_to_token_account, + get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, }; use solana_signer::Signer; -pub mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use lending::constants::{ BPS_DENOMINATOR, LENDING_MARKET_SEED, LIQUIDITY_VAULT_SEED, MINIMUM_SHARES, OBLIGATION_SEED, OBLIGATION_SHARE_VAULT_SEED, PRICE_FEED_SEED, RESERVE_SEED, SHARE_MINT_SEED, @@ -88,7 +84,7 @@ fn send( signers: &[&Keypair], payer: &Pubkey, ) -> Result<(), String> { - send_transaction_from_instructions(svm, instructions, signers, payer) + send_transaction_from_instructions(svm, instructions, signers, payer, None) .map_err(|thrown| format!("{thrown:?}")) } @@ -118,7 +114,7 @@ impl Env { svm.add_program(lending::id(), program_bytes).unwrap(); let owner = create_wallet(&mut svm, 1_000_000_000_000).unwrap(); - let quote_mint = create_token_mint(&mut svm, &owner, 6, None).unwrap(); + let quote_mint = create_token_mint(&mut svm, &owner, 6, None, None).unwrap(); // The market is seeded by its market_id index alone (no owner). Market 0. let market_id: u64 = 0; let market = pda(&[LENDING_MARKET_SEED, &market_id.to_le_bytes()]); @@ -149,7 +145,7 @@ impl Env { /// exercise cross-market isolation. pub fn init_market_for(&mut self, market_owner: &Keypair) -> Pubkey { let env_owner = self.owner.insecure_clone(); - let quote_mint = create_token_mint(&mut self.svm, &env_owner, 6, None).unwrap(); + let quote_mint = create_token_mint(&mut self.svm, &env_owner, 6, None, None).unwrap(); // A distinct id from the env's market 0, since the id is the market's // global identifier (the owner is not part of the seed). let market_id: u64 = 1; @@ -201,7 +197,7 @@ impl Env { config: ReserveConfig, ) -> Result { let env_owner = self.owner.insecure_clone(); - let mint = create_token_mint(&mut self.svm, &env_owner, decimals, None).unwrap(); + let mint = create_token_mint(&mut self.svm, &env_owner, decimals, None, None).unwrap(); self.set_price_for( market_owner, market, @@ -407,10 +403,18 @@ impl Env { pub fn fund(&mut self, user: &Keypair, mint: Pubkey, amount: u64) -> Pubkey { let owner = self.owner.insecure_clone(); let token_account = - create_associated_token_account(&mut self.svm, &user.pubkey(), &mint, user).unwrap(); - if amount > 0 { - mint_tokens_to_token_account(&mut self.svm, &mint, &token_account, amount, &owner) + create_associated_token_account(&mut self.svm, &user.pubkey(), &mint, user, None) .unwrap(); + if amount > 0 { + mint_tokens_to_token_account( + &mut self.svm, + &mint, + &token_account, + amount, + &owner, + None, + ) + .unwrap(); } token_account } @@ -442,6 +446,7 @@ impl Env { &user.pubkey(), &handle.share_mint, user, + None, ) .unwrap(); } @@ -842,6 +847,7 @@ impl Env { &liquidator.pubkey(), &collateral.share_mint, liquidator, + None, ) .unwrap(); } @@ -914,8 +920,14 @@ impl Env { let owner = self.owner.insecure_clone(); let owner_liquidity = ata(&owner.pubkey(), &handle.mint); if self.svm.get_account(&owner_liquidity).is_none() { - create_associated_token_account(&mut self.svm, &owner.pubkey(), &handle.mint, &owner) - .unwrap(); + create_associated_token_account( + &mut self.svm, + &owner.pubkey(), + &handle.mint, + &owner, + None, + ) + .unwrap(); } let refresh = self.refresh_reserve_ix(handle); let collect = Instruction { diff --git a/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs b/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/lending/anchor-v1/programs/lending/tests/common/transaction_v1.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs b/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs index 622d71068..40fe3c1af 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs @@ -59,6 +59,7 @@ fn raw_token_donation_does_not_inflate_exchange_rate() { &usdc.liquidity_vault, amount, &owner, + None, ) .unwrap(); diff --git a/finance/lending/anchor-v1/programs/lending/tests/test_security.rs b/finance/lending/anchor-v1/programs/lending/tests/test_security.rs index b6e5a31ae..3af09eae0 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/test_security.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/test_security.rs @@ -67,11 +67,12 @@ fn non_owner_cannot_write_market_price_feed() { } .data(), }; - let result = common::transaction_v1::send_transaction_from_instructions( + let result = solana_kite::send_transaction_from_instructions( &mut env.svm, vec![instruction], &[&attacker], &attacker.pubkey(), + None, ); // `SetPrice` binds the signer with `has_one = owner` on the market, so // the refusal is Anchor's constraint error, not one of the program's. diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml b/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml index e6af2488a..f2660292d 100644 --- a/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml +++ b/finance/managed-fund/anchor-v1/programs/managed-fund/Cargo.toml @@ -33,10 +33,8 @@ litesvm = "0.16.0" solana-account = "4.3.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs index ccdd39e08..9f08968db 100644 --- a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs +++ b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -17,6 +13,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -242,9 +239,9 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { let payer = create_wallet(&mut svm, 100_000_000_000).unwrap(); let manager = create_wallet(&mut svm, 10_000_000_000).unwrap(); - let usdc_mint = create_token_mint(&mut svm, &payer, USDC_DECIMALS, None).unwrap(); - let tsla_mint = create_token_mint(&mut svm, &payer, tsla_decimals, None).unwrap(); - let nvda_mint = create_token_mint(&mut svm, &payer, ASSET_DECIMALS, None).unwrap(); + let usdc_mint = create_token_mint(&mut svm, &payer, USDC_DECIMALS, None, None).unwrap(); + let tsla_mint = create_token_mint(&mut svm, &payer, tsla_decimals, None, None).unwrap(); + let nvda_mint = create_token_mint(&mut svm, &payer, ASSET_DECIMALS, None, None).unwrap(); let (router_config_pda, _) = Pubkey::find_program_address(&[b"router_config"], &router_program_id); @@ -261,7 +258,8 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { &[], ) .unwrap(); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } let (fund_pda, _) = Pubkey::find_program_address( @@ -308,8 +306,14 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_router_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_router_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); for (mint, rate, rate_pda) in [ (tsla_mint, TSLA_RATE, tsla_rate_pda), @@ -335,7 +339,8 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } mint_tokens_to_token_account( @@ -344,6 +349,7 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { &router_usdc_treasury, 10_000_000_000u64, &payer, + None, ) .unwrap(); @@ -363,6 +369,7 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { vec![init_registry_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); @@ -382,7 +389,8 @@ fn setup_with_tsla_decimals(tsla_decimals: u8) -> TestContext { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey(), None) + .unwrap(); } TestContext { @@ -451,6 +459,7 @@ fn init_fund(ctx: &mut TestContext, fee_bps: u16, slippage_bps: u16, router: Pub vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ) .unwrap(); } @@ -486,6 +495,7 @@ fn add_asset( vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ) } @@ -590,7 +600,8 @@ fn do_deposit( ) -> Pubkey { let remaining = deposit_remaining(ctx); let ix = deposit_instruction(ctx, user, usdc_amount, minimum_shares, remaining); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey(), None) + .unwrap(); derive_ata(&user.pubkey(), &ctx.share_mint_pda) } @@ -603,7 +614,8 @@ fn do_deposit_tsla_only( ) -> Pubkey { let remaining = deposit_remaining_tsla(ctx); let ix = deposit_instruction(ctx, user, usdc_amount, minimum_shares, remaining); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey(), None) + .unwrap(); derive_ata(&user.pubkey(), &ctx.share_mint_pda) } @@ -630,8 +642,14 @@ fn set_router_rate(ctx: &mut TestContext, mint: Pubkey, rate: u64, rate_pda: Pub } .to_account_metas(None), ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&ctx.payer], + &ctx.payer.pubkey(), + None, + ) + .unwrap(); } /// Move NVDAx's price: rewrite its Pyth feed and update the router rate to match. @@ -662,6 +680,7 @@ fn set_weight( vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ) } @@ -730,7 +749,8 @@ fn donate_usdc(ctx: &mut TestContext, donor: &Keypair, amount: u64) { amount, ) .unwrap(); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[donor], &donor.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[donor], &donor.pubkey(), None) + .unwrap(); } /// A holder's position valued in USDC minor units at the test's starting prices @@ -807,7 +827,13 @@ fn try_rebalance( ) -> Result<(), solana_kite::SolanaKiteError> { let stranger = create_wallet(&mut ctx.svm, 1_000_000_000).unwrap(); let ix = rebalance_instruction(ctx, &stranger, sell_index, buy_index); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&stranger], &stranger.pubkey()) + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&stranger], + &stranger.pubkey(), + None, + ) } fn do_rebalance(ctx: &mut TestContext, sell_index: u8, buy_index: u8) { @@ -884,22 +910,34 @@ fn do_collect_fees(ctx: &mut TestContext) -> Pubkey { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&ctx.payer], + &ctx.payer.pubkey(), + None, + ) + .unwrap(); manager_share } fn fund_user(ctx: &mut TestContext, usdc_amount: u64) -> Keypair { let user = create_wallet(&mut ctx.svm, 10_000_000_000).unwrap(); - let user_usdc = - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.usdc_mint, &ctx.payer) - .unwrap(); + let user_usdc = create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.usdc_mint, + &ctx.payer, + None, + ) + .unwrap(); mint_tokens_to_token_account( &mut ctx.svm, &ctx.usdc_mint, &user_usdc, usdc_amount, &ctx.payer, + None, ) .unwrap(); user @@ -935,7 +973,8 @@ fn test_add_asset_rejects_unapproved() { init_fund(&mut ctx, FEE_BPS, SLIPPAGE_BPS, router); // A mint that was never approved: its approved_asset PDA does not exist. - let rogue_mint = create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None).unwrap(); + let rogue_mint = + create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None, None).unwrap(); let (rogue_entry, _) = Pubkey::find_program_address( &[ b"approved_asset", @@ -974,7 +1013,7 @@ fn test_add_asset_rejects_weight_overflow() { /// arbitrary pubkey: callers that never value this asset (e.g. the cap boundary test) /// do not need a real feed account. fn create_and_approve_mint(ctx: &mut TestContext) -> (Pubkey, Pubkey) { - let mint = create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None).unwrap(); + let mint = create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None, None).unwrap(); let (entry, _) = Pubkey::find_program_address( &[b"approved_asset", ctx.registry_pda.as_ref(), mint.as_ref()], &ctx.fund_program_id, @@ -994,8 +1033,14 @@ fn create_and_approve_mint(ctx: &mut TestContext) -> (Pubkey, Pubkey) { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&ctx.payer], + &ctx.payer.pubkey(), + None, + ) + .unwrap(); (mint, entry) } @@ -1058,6 +1103,7 @@ fn test_initialize_rejects_excessive_fee() { vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ); assert_program_error( r, @@ -1098,6 +1144,7 @@ fn test_initialize_rejects_excessive_slippage() { vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ); assert_program_error( r, @@ -1176,7 +1223,8 @@ fn test_deposit_rejects_underallocated() { let user = fund_user(&mut ctx, 10_000_000); let ix = deposit_instruction(&ctx, &user, 10_000_000, 1, deposit_remaining_tsla(&ctx)); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_program_error( r, FundError::FundNotFullyAllocated, @@ -1213,7 +1261,8 @@ fn test_deposit_rejects_slippage() { let user = fund_user(&mut ctx, 10_000_000); let ix = deposit_instruction(&ctx, &user, 10_000_000, 1, deposit_remaining(&ctx)); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_router_error( r, RouterError::SlippageExceeded, @@ -1234,7 +1283,8 @@ fn test_deposit_rejects_unregistered_router() { let user = fund_user(&mut ctx, 10_000_000); let ix = deposit_instruction(&ctx, &user, 10_000_000, 1, deposit_remaining(&ctx)); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_program_error( r, FundError::InvalidSwapRouter, @@ -1276,7 +1326,7 @@ fn test_deposit_rejects_price_from_before_restart() { let ix = deposit_instruction(&ctx, &user, 10_000_000, 1, deposit_remaining(&ctx)); assert_program_error( - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()), + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None), FundError::PricePredatesRestart, "a deposit priced before the restart must fail", ); @@ -1458,10 +1508,22 @@ fn test_withdraw() { // User needs token accounts for each asset paid in kind. let user_usdc = derive_ata(&user.pubkey(), &ctx.usdc_mint); - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.tsla_mint, &ctx.payer) - .unwrap(); - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.nvda_mint, &ctx.payer) - .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.tsla_mint, + &ctx.payer, + None, + ) + .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.nvda_mint, + &ctx.payer, + None, + ) + .unwrap(); let mut metas = managed_fund::accounts::WithdrawAccountConstraints { user: user.pubkey(), @@ -1487,7 +1549,8 @@ fn test_withdraw() { .data(), metas, ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None) + .unwrap(); // Sole holder withdraws everything in kind: all 0.016 TSLAx + 0.03333333 // NVDAx, no USDC. @@ -1512,10 +1575,22 @@ fn test_withdraw_rejects_slippage() { let shares = get_token_account_balance(&ctx.svm, &user_share).unwrap(); let user_usdc = derive_ata(&user.pubkey(), &ctx.usdc_mint); - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.tsla_mint, &ctx.payer) - .unwrap(); - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.nvda_mint, &ctx.payer) - .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.tsla_mint, + &ctx.payer, + None, + ) + .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.nvda_mint, + &ctx.payer, + None, + ) + .unwrap(); let mut metas = managed_fund::accounts::WithdrawAccountConstraints { user: user.pubkey(), @@ -1543,7 +1618,8 @@ fn test_withdraw_rejects_slippage() { .data(), metas, ); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_program_error( r, FundError::UsdcSlippage, @@ -1561,7 +1637,8 @@ fn test_deposit_rejects_incomplete_assets() { // Only one asset's accounts supplied (5) for a two-asset fund (needs 10). let ix = deposit_instruction(&ctx, &user, amount, 1, deposit_remaining_tsla(&ctx)); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_program_error( r, FundError::IncompleteAssetAccounts, @@ -1570,10 +1647,22 @@ fn test_deposit_rejects_incomplete_assets() { } fn do_withdraw(ctx: &mut TestContext, user: &Keypair, shares: u64, min_usdc_out: u64) { - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.tsla_mint, &ctx.payer) - .unwrap(); - create_associated_token_account(&mut ctx.svm, &user.pubkey(), &ctx.nvda_mint, &ctx.payer) - .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.tsla_mint, + &ctx.payer, + None, + ) + .unwrap(); + create_associated_token_account( + &mut ctx.svm, + &user.pubkey(), + &ctx.nvda_mint, + &ctx.payer, + None, + ) + .unwrap(); let user_usdc = derive_ata(&user.pubkey(), &ctx.usdc_mint); let user_share = derive_ata(&user.pubkey(), &ctx.share_mint_pda); let mut metas = managed_fund::accounts::WithdrawAccountConstraints { @@ -1599,7 +1688,8 @@ fn do_withdraw(ctx: &mut TestContext, user: &Keypair, shares: u64, min_usdc_out: .data(), metas, ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey(), None) + .unwrap(); } #[test] @@ -1616,7 +1706,8 @@ fn test_set_weight_retire() { let user = fund_user(&mut ctx, 100_000_000); let ix = deposit_instruction(&ctx, &user, 100_000_000, 1, deposit_remaining(&ctx)); - let r = send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()); + let r = + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None); assert_program_error( r, FundError::FundNotFullyAllocated, @@ -1678,6 +1769,7 @@ fn test_set_weight_rejects_non_manager() { vec![ix], &[&intruder], &intruder.pubkey(), + None, ); // The manager is bound to the fund's stored `manager` by an Anchor // constraint, so the refusal is Anchor's, not one of the program's errors. @@ -1861,6 +1953,7 @@ fn test_deposit_rejects_leg_that_buys_nothing() { vec![ix], &[&attacker], &attacker.pubkey(), + None, ); let err = format!( "{:?}", @@ -1896,7 +1989,8 @@ fn donate_token( amount, ) .unwrap(); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[donor], &donor.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[donor], &donor.pubkey(), None) + .unwrap(); } /// A fund sitting at its target weights has nothing to rebalance, in either @@ -1981,7 +2075,7 @@ struct ExtraAsset { } fn create_routable_asset(ctx: &mut TestContext, price: i64, rate: u64) -> ExtraAsset { - let mint = create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None).unwrap(); + let mint = create_token_mint(&mut ctx.svm, &ctx.payer, ASSET_DECIMALS, None, None).unwrap(); let ix = spl_token::instruction::set_authority( &spl_token::ID, &mint, @@ -1991,8 +2085,14 @@ fn create_routable_asset(ctx: &mut TestContext, price: i64, rate: u64) -> ExtraA &[], ) .unwrap(); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&ctx.payer], + &ctx.payer.pubkey(), + None, + ) + .unwrap(); let (rate_pda, _) = Pubkey::find_program_address(&[b"rate", mint.as_ref()], &ctx.router_program_id); set_router_rate(ctx, mint, rate, rate_pda); @@ -2014,8 +2114,14 @@ fn create_routable_asset(ctx: &mut TestContext, price: i64, rate: u64) -> ExtraA } .to_account_metas(None), ); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut ctx.svm, + vec![ix], + &[&ctx.payer], + &ctx.payer.pubkey(), + None, + ) + .unwrap(); let vault = derive_ata(&ctx.fund_pda, &mint); ExtraAsset { mint, @@ -2052,7 +2158,8 @@ fn test_rebalance_refuses_buying_overweight_asset() { )); let alice = fund_user(&mut ctx, 1_000_000_000); let ix = deposit_instruction(&ctx, &alice, 1_000_000_000, 1, remaining.clone()); - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&alice], &alice.pubkey()).unwrap(); + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&alice], &alice.pubkey(), None) + .unwrap(); // NVDAx and the third asset both rise 30%. NVDAx is about $520 of $1,180 // against a $472 target; the third asset $260 against $236; TSLAx $400 @@ -2069,6 +2176,7 @@ fn test_rebalance_refuses_buying_overweight_asset() { vec![into_third], &[&stranger], &stranger.pubkey(), + None, ); assert_program_error( r, @@ -2083,6 +2191,7 @@ fn test_rebalance_refuses_buying_overweight_asset() { vec![into_itself], &[&stranger], &stranger.pubkey(), + None, ); assert_program_error( r, @@ -2097,6 +2206,7 @@ fn test_rebalance_refuses_buying_overweight_asset() { vec![into_tsla], &[&stranger], &stranger.pubkey(), + None, ) .unwrap(); assert!(read_fund(&ctx).asset_holdings[0] > 160_000_000); @@ -2120,6 +2230,7 @@ fn test_rebalance_rejects_incomplete_assets() { vec![ix], &[&stranger], &stranger.pubkey(), + None, ); assert_program_error( r, @@ -2232,6 +2343,7 @@ fn test_initialize_rejects_threshold_out_of_range() { vec![ix], &[&ctx.manager], &ctx.manager.pubkey(), + None, ); assert_program_error( r, @@ -2337,7 +2449,7 @@ fn test_wide_confidence_price_rejected() { let bob = fund_user(&mut ctx, 480_000_000); let ix = deposit_instruction(&ctx, &bob, 480_000_000, 1, deposit_remaining(&ctx)); assert_program_error( - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&bob], &bob.pubkey()), + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&bob], &bob.pubkey(), None), FundError::OracleConfidenceTooWide, "a deposit priced from a wide-confidence feed must fail", ); @@ -2389,7 +2501,7 @@ fn test_partially_verified_price_rejected() { write_partially_verified_price_feed(&mut ctx.svm, ctx.price_feed_nvda, NVDA_PRICE, 2); let ix = deposit_instruction(&ctx, &user, amount, amount, deposit_remaining(&ctx)); assert_program_error( - send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey()), + send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&user], &user.pubkey(), None), FundError::PriceNotFullyVerified, "a deposit priced from a partially verified update must fail", ); diff --git a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs b/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/managed-fund/anchor-v1/programs/managed-fund/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/managed-fund/anchor-v1/programs/mock-swap-router/Cargo.toml b/finance/managed-fund/anchor-v1/programs/mock-swap-router/Cargo.toml index d6938bb59..bb930a85f 100644 --- a/finance/managed-fund/anchor-v1/programs/mock-swap-router/Cargo.toml +++ b/finance/managed-fund/anchor-v1/programs/mock-swap-router/Cargo.toml @@ -27,7 +27,7 @@ anchor-spl = "1.2.0" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" [lints.rust] diff --git a/finance/options/anchor-v1/programs/options/Cargo.toml b/finance/options/anchor-v1/programs/options/Cargo.toml index b7e9bc270..e10d1b5a7 100644 --- a/finance/options/anchor-v1/programs/options/Cargo.toml +++ b/finance/options/anchor-v1/programs/options/Cargo.toml @@ -35,14 +35,13 @@ spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -# The suite sends each instruction through LiteSVM itself rather than through -# solana-kite's wrapper, so it can read the transaction's metadata back: the -# inner instructions say how many token transfers a handler made. Same major -# versions as litesvm 0.16 and solana-kite 0.5 use, so one copy of each is -# linked. -solana-transaction = "4" -solana-message = "4" +solana-kite = "0.6.0" +# The suite builds each transaction with solana-kite and sends it through +# LiteSVM itself rather than through solana-kite's sender, so it can read the +# transaction's metadata back: the inner instructions say how many token +# transfers a handler made. This crate names the compute unit limit those +# transactions get; same version solana-kite 0.6 builds against. +solana-compute-budget = "4.2" borsh = "1.6.1" [lints.rust] diff --git a/finance/options/anchor-v1/programs/options/tests/test_options.rs b/finance/options/anchor-v1/programs/options/tests/test_options.rs index 4e60ff29e..4aa306842 100644 --- a/finance/options/anchor-v1/programs/options/tests/test_options.rs +++ b/finance/options/anchor-v1/programs/options/tests/test_options.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::{send_transaction_from_instructions, v1_transaction}; - use { anchor_lang::{ error::{ErrorCode as AnchorErrorCode, ERROR_CODE_OFFSET}, @@ -14,10 +10,12 @@ use { instructions::write_option::OptionTerms, state::{Market as MarketState, OptionContract, OptionKind, OptionStatus}, }, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, solana_keypair::Keypair, solana_kite::{ - create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, + build_transaction_from_instructions, create_associated_token_account, create_token_mint, + create_wallet, get_token_account_balance, get_transaction_config, + mint_tokens_to_token_account, send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -163,11 +161,12 @@ impl Venue { let payer = create_wallet(&mut svm, 100_000_000_000).unwrap(); let admin = create_wallet(&mut svm, 100_000_000_000).unwrap(); - let underlying_mint = create_token_mint(&mut svm, &admin, NVDAX_DECIMALS, None).unwrap(); + let underlying_mint = + create_token_mint(&mut svm, &admin, NVDAX_DECIMALS, None, None).unwrap(); let quote_mint = if same_mint { underlying_mint } else { - create_token_mint(&mut svm, &admin, USDC_DECIMALS, None).unwrap() + create_token_mint(&mut svm, &admin, USDC_DECIMALS, None, None).unwrap() }; let market = Pubkey::find_program_address( @@ -200,6 +199,7 @@ impl Venue { vec![initialize_market], &[&admin], &admin.pubkey(), + None, ) .map_err(|error| format!("{error:?}"))?; @@ -292,6 +292,7 @@ impl Venue { &keypair.pubkey(), &self.underlying_mint, &self.payer, + None, ) .unwrap() } else { @@ -302,6 +303,7 @@ impl Venue { &keypair.pubkey(), &self.quote_mint, &self.payer, + None, ) .unwrap(); if underlying > 0 { @@ -311,6 +313,7 @@ impl Venue { &underlying_account, underlying, &self.admin, + None, ) .unwrap(); } @@ -321,6 +324,7 @@ impl Venue { "e_account, quote, &self.admin, + None, ) .unwrap(); } @@ -351,7 +355,14 @@ impl Venue { instruction: Instruction, signer: &Keypair, ) -> Result { - let transaction = v1_transaction(&self.svm, &[instruction], &[signer], &signer.pubkey()); + let transaction = build_transaction_from_instructions( + &self.svm, + &[instruction], + &[signer], + &signer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); self.svm .send_transaction(transaction) .map_err(|failed| format!("{:?}", failed.err)) @@ -985,10 +996,7 @@ fn test_put_writer_without_an_underlying_account_writes_and_cancels() { let option = venue.write_put(&carol); assert_eq!(venue.balance(&carol.underlying), 0); - assert_eq!( - venue.balance(&carol.quote), - STANDARD_USDC - PUT_STRIKE_AMOUNT - ); + assert_eq!(venue.balance(&carol.quote), STANDARD_USDC - PUT_STRIKE_AMOUNT); let option_rent = venue.lamports(&option); let carol_lamports_before_cancel = venue.lamports(&carol.pubkey()); diff --git a/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs b/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/options/anchor-v1/programs/options/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/order-book/anchor-v1/programs/order-book/Cargo.toml b/finance/order-book/anchor-v1/programs/order-book/Cargo.toml index 68959ebb6..8519f1c81 100644 --- a/finance/order-book/anchor-v1/programs/order-book/Cargo.toml +++ b/finance/order-book/anchor-v1/programs/order-book/Cargo.toml @@ -35,12 +35,12 @@ static_assertions = "1.1" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" -# The depth tests send transactions themselves to read the compute units -# each one used, which solana-kite's helper does not return. Same versions -# solana-kite builds against. -solana-transaction = "4.1.5" -solana-message = "4.2.4" +solana-kite = "0.6.0" +# The depth tests build transactions with solana-kite and send them through +# LiteSVM themselves to read the compute units each one used, which +# solana-kite's sender does not return. This crate names the compute unit +# limit those transactions get; same version solana-kite 0.6 builds against. +solana-compute-budget = "4.2" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs index a8b765a56..0ff044edf 100644 --- a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs @@ -13,10 +13,6 @@ //! `close_market_user` give a finished order's and an idle user's rent back //! to the owner, and refuse while anything still rests or is owed. -mod transaction_v1; - -use transaction_v1::{send_transaction_from_instructions, v1_transaction}; - use { anchor_lang::{ solana_program::{ @@ -27,10 +23,12 @@ use { Discriminator, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_compute_budget::compute_budget_limits::MAX_COMPUTE_UNIT_LIMIT, solana_keypair::Keypair, solana_kite::{ - create_associated_token_account, create_token_mint, create_wallet, get_sol_balance, - get_token_account_balance, mint_tokens_to_token_account, + build_transaction_from_instructions, create_associated_token_account, create_token_mint, + create_wallet, get_sol_balance, get_token_account_balance, get_transaction_config, + mint_tokens_to_token_account, send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -176,19 +174,23 @@ fn full_setup() -> Scenario { let buyer = create_wallet(&mut svm, 10_000_000_000).unwrap(); let seller = create_wallet(&mut svm, 10_000_000_000).unwrap(); - let base_mint = create_token_mint(&mut svm, &authority, BASE_DECIMALS, None).unwrap(); - let quote_mint = create_token_mint(&mut svm, &authority, QUOTE_DECIMALS, None).unwrap(); + let base_mint = create_token_mint(&mut svm, &authority, BASE_DECIMALS, None, None).unwrap(); + let quote_mint = create_token_mint(&mut svm, &authority, QUOTE_DECIMALS, None, None).unwrap(); // Create and fund every trader's ATAs up-front so individual tests do // not need to worry about mint/ATA side effects, only about order-book state. let buyer_base_ata = - create_associated_token_account(&mut svm, &buyer.pubkey(), &base_mint, &payer).unwrap(); + create_associated_token_account(&mut svm, &buyer.pubkey(), &base_mint, &payer, None) + .unwrap(); let buyer_quote_ata = - create_associated_token_account(&mut svm, &buyer.pubkey(), "e_mint, &payer).unwrap(); + create_associated_token_account(&mut svm, &buyer.pubkey(), "e_mint, &payer, None) + .unwrap(); let seller_base_ata = - create_associated_token_account(&mut svm, &seller.pubkey(), &base_mint, &payer).unwrap(); + create_associated_token_account(&mut svm, &seller.pubkey(), &base_mint, &payer, None) + .unwrap(); let seller_quote_ata = - create_associated_token_account(&mut svm, &seller.pubkey(), "e_mint, &payer).unwrap(); + create_associated_token_account(&mut svm, &seller.pubkey(), "e_mint, &payer, None) + .unwrap(); mint_tokens_to_token_account( &mut svm, @@ -196,6 +198,7 @@ fn full_setup() -> Scenario { &seller_base_ata, TRADER_STARTING_BALANCE, &authority, + None, ) .unwrap(); mint_tokens_to_token_account( @@ -204,6 +207,7 @@ fn full_setup() -> Scenario { &buyer_quote_ata, TRADER_STARTING_BALANCE, &authority, + None, ) .unwrap(); @@ -540,6 +544,7 @@ fn initialize_market_and_users(sc: &mut Scenario) { vec![create_ix, init_ix], &[&sc.authority, &sc.order_book], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -549,6 +554,7 @@ fn initialize_market_and_users(sc: &mut Scenario) { vec![buyer_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -558,6 +564,7 @@ fn initialize_market_and_users(sc: &mut Scenario) { vec![seller_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); } @@ -584,6 +591,7 @@ fn initialize_market_sets_market_and_order_book() { vec![create_ix, ix], &[&sc.authority, &sc.order_book], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -633,6 +641,7 @@ fn initialize_market_user_tracks_market_and_owner() { vec![create_ix, init_ix], &[&sc.authority, &sc.order_book], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -642,6 +651,7 @@ fn initialize_market_user_tracks_market_and_owner() { vec![create_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -670,8 +680,14 @@ fn place_bid_moves_quote_into_vault() { BID_PRICE, BID_QUANTITY, ); - send_transaction_from_instructions(&mut sc.svm, vec![ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); // A bid locks price * quantity * quote_lot_size raw quote tokens. let locked_quote = BID_PRICE * BID_QUANTITY * QUOTE_LOT_SIZE; @@ -715,8 +731,14 @@ fn place_ask_moves_base_into_vault() { ASK_PRICE, ASK_QUANTITY, ); - send_transaction_from_instructions(&mut sc.svm, vec![ix], &[&sc.seller], &sc.seller.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![ix], + &[&sc.seller], + &sc.seller.pubkey(), + None, + ) + .unwrap(); // An ask locks quantity * base_lot_size raw base tokens in the base vault. assert_eq!( @@ -782,6 +804,7 @@ fn place_order_rejects_unaligned_tick() { vec![create_ix, init_ix], &[&sc.authority, &sc.order_book], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -791,6 +814,7 @@ fn place_order_rejects_unaligned_tick() { vec![create_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -837,6 +861,7 @@ fn place_order_rejects_below_min_order_size() { vec![create_ix, init_ix], &[&sc.authority, &sc.order_book], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -846,6 +871,7 @@ fn place_order_rejects_below_min_order_size() { vec![create_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -895,6 +921,7 @@ fn cancel_ask_credits_unsettled_base() { vec![place_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -909,6 +936,7 @@ fn cancel_ask_credits_unsettled_base() { vec![cancel_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -949,6 +977,7 @@ fn cancel_order_rejects_non_owner() { vec![place_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -997,6 +1026,7 @@ fn settle_funds_moves_unsettled_base_to_user() { vec![place_ix, cancel_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1012,6 +1042,7 @@ fn settle_funds_moves_unsettled_base_to_user() { vec![settle_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1057,6 +1088,7 @@ fn cancel_and_settle_bid_refunds_full_quote() { vec![place_ix, cancel_ix, settle_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1106,6 +1138,7 @@ fn settle_funds_rejects_fee_vault_substituted_for_quote_vault() { vec![place_ix, cancel_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1340,6 +1373,7 @@ fn taker_bid_fully_crosses_best_ask() { vec![maker_ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1362,6 +1396,7 @@ fn taker_bid_fully_crosses_best_ask() { vec![taker_bid_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1417,6 +1452,7 @@ fn taker_ask_fully_crosses_best_bid() { vec![maker_bid_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1438,6 +1474,7 @@ fn taker_ask_fully_crosses_best_bid() { vec![taker_ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1483,6 +1520,7 @@ fn taker_partially_fills_resting_order_rest_stays_on_book() { vec![ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1499,8 +1537,14 @@ fn taker_partially_fills_resting_order_rest_stays_on_book() { TAKER_BID_QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![bid_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); // Maker order: still PartiallyFilled, filled_quantity == TAKER_BID_QUANTITY. let maker_order = order_pda(&sc.program_id, &sc.market, MAKER_ASK_ID); @@ -1553,6 +1597,7 @@ fn taker_partially_filled_remainder_rests_on_book() { vec![ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1569,8 +1614,14 @@ fn taker_partially_filled_remainder_rests_on_book() { TAKER_BID_QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![bid_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); // Maker ask is fully filled. let maker_order = order_pda(&sc.program_id, &sc.market, MAKER_ASK_ID); @@ -1633,6 +1684,7 @@ fn taker_crosses_multiple_resting_orders_best_price_first() { vec![ask_one_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); let ask_two_ix = build_place_order_ix( @@ -1651,6 +1703,7 @@ fn taker_crosses_multiple_resting_orders_best_price_first() { vec![ask_two_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1675,6 +1728,7 @@ fn taker_crosses_multiple_resting_orders_best_price_first() { vec![taker_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1725,6 +1779,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { &second_seller.pubkey(), &sc.base_mint, &sc.payer, + None, ) .unwrap(); let second_seller_quote_ata = create_associated_token_account( @@ -1732,6 +1787,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { &second_seller.pubkey(), &sc.quote_mint, &sc.payer, + None, ) .unwrap(); mint_tokens_to_token_account( @@ -1740,6 +1796,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { &second_seller_base_ata, TRADER_STARTING_BALANCE, &sc.authority, + None, ) .unwrap(); let second_seller_market_user = @@ -1750,6 +1807,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { vec![__ix1], &[&second_seller], &second_seller.pubkey(), + None, ) .unwrap(); @@ -1775,6 +1833,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { vec![__ix2], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); // Seller 2 second in at the same price. @@ -1794,6 +1853,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { vec![__ix3], &[&second_seller], &second_seller.pubkey(), + None, ) .unwrap(); @@ -1816,6 +1876,7 @@ fn resting_orders_at_same_price_fill_by_time_priority() { vec![taker_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1861,6 +1922,7 @@ fn taker_bid_gets_price_improvement_from_resting_ask() { vec![__ix4], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1883,6 +1945,7 @@ fn taker_bid_gets_price_improvement_from_resting_ask() { vec![taker_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1935,6 +1998,7 @@ fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { vec![maker_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -1956,6 +2020,7 @@ fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { vec![taker_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .unwrap(); @@ -1997,6 +2062,7 @@ fn fee_vault_receives_exactly_bps_of_taker_gross() { vec![__ix5], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -2013,8 +2079,14 @@ fn fee_vault_receives_exactly_bps_of_taker_gross() { QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix6], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix6], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault).unwrap(), @@ -2034,6 +2106,7 @@ fn authority_can_withdraw_fees_after_match() { &sc.authority.pubkey(), &sc.quote_mint, &sc.payer, + None, ) .unwrap(); @@ -2059,6 +2132,7 @@ fn authority_can_withdraw_fees_after_match() { vec![__ix7], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -2075,8 +2149,14 @@ fn authority_can_withdraw_fees_after_match() { QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix8], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix8], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault).unwrap(), @@ -2089,6 +2169,7 @@ fn authority_can_withdraw_fees_after_match() { vec![withdraw_ix], &[&sc.authority], &sc.authority.pubkey(), + None, ) .unwrap(); @@ -2133,6 +2214,7 @@ fn withdraw_fees_rejects_a_non_authority_signer() { vec![ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -2149,8 +2231,14 @@ fn withdraw_fees_rejects_a_non_authority_signer() { QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![bid_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault).unwrap(), EXPECTED_FEE @@ -2210,6 +2298,7 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { vec![__ix9], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); const TAKER_BID_ID: u64 = 2; @@ -2225,8 +2314,14 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix10], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix10], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); // Settle both sides. let __ix11 = build_settle_funds_ix( @@ -2236,8 +2331,14 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { sc.buyer_base_ata, sc.buyer_quote_ata, ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix11], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix11], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); let __ix12 = build_settle_funds_ix( &sc, &sc.seller.pubkey(), @@ -2250,6 +2351,7 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { vec![__ix12], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -2343,7 +2445,14 @@ fn best_ask_depth(svm: &LiteSVM, order_book: &Pubkey) -> usize { // Send one instruction and return the compute units it used. fn send_and_measure(svm: &mut LiteSVM, instruction: Instruction, signer: &Keypair) -> u64 { - let transaction = v1_transaction(svm, &[instruction], &[signer], &signer.pubkey()); + let transaction = build_transaction_from_instructions( + svm, + &[instruction], + &[signer], + &signer.pubkey(), + get_transaction_config(MAX_COMPUTE_UNIT_LIMIT), + ) + .unwrap(); svm.send_transaction(transaction) .unwrap() .compute_units_consumed @@ -2368,18 +2477,29 @@ fn order_bump_search_units(program_id: &Pubkey, market: &Pubkey, order_id: u64) // A fresh seller with base tokens and a market user account. fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Pubkey, Pubkey, Pubkey) { let seller = create_wallet(&mut sc.svm, 10_000_000_000).unwrap(); - let base_ata = - create_associated_token_account(&mut sc.svm, &seller.pubkey(), &sc.base_mint, &sc.payer) - .unwrap(); - let quote_ata = - create_associated_token_account(&mut sc.svm, &seller.pubkey(), &sc.quote_mint, &sc.payer) - .unwrap(); + let base_ata = create_associated_token_account( + &mut sc.svm, + &seller.pubkey(), + &sc.base_mint, + &sc.payer, + None, + ) + .unwrap(); + let quote_ata = create_associated_token_account( + &mut sc.svm, + &seller.pubkey(), + &sc.quote_mint, + &sc.payer, + None, + ) + .unwrap(); mint_tokens_to_token_account( &mut sc.svm, &sc.base_mint, &base_ata, TRADER_STARTING_BALANCE, &sc.authority, + None, ) .unwrap(); let market_user = market_user_pda(&sc.program_id, &sc.market, &seller.pubkey()); @@ -2389,6 +2509,7 @@ fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Pubkey, Pubkey, Pubkey) { vec![instruction], &[&seller], &seller.pubkey(), + None, ) .unwrap(); (seller, base_ata, quote_ata, market_user) @@ -2437,6 +2558,7 @@ fn run_probes_at_bottom_of_book(build_chain: bool) -> ProbeCosts { vec![instruction], &[&seller], &seller.pubkey(), + None, ) .unwrap(); next_order_id += 1; @@ -2578,7 +2700,7 @@ fn failure_text( payer: &Pubkey, must_fail: &str, ) -> String { - match send_transaction_from_instructions(svm, instructions, signers, payer) { + match send_transaction_from_instructions(svm, instructions, signers, payer, None) { Ok(_) => panic!("{must_fail}"), Err(error) => format!("{error:?}"), } @@ -2603,23 +2725,40 @@ struct Trader { fn create_trader(sc: &mut Scenario) -> Trader { let keypair = create_wallet(&mut sc.svm, 10_000_000_000).unwrap(); - let base_ata = - create_associated_token_account(&mut sc.svm, &keypair.pubkey(), &sc.base_mint, &sc.payer) - .unwrap(); - let quote_ata = - create_associated_token_account(&mut sc.svm, &keypair.pubkey(), &sc.quote_mint, &sc.payer) - .unwrap(); + let base_ata = create_associated_token_account( + &mut sc.svm, + &keypair.pubkey(), + &sc.base_mint, + &sc.payer, + None, + ) + .unwrap(); + let quote_ata = create_associated_token_account( + &mut sc.svm, + &keypair.pubkey(), + &sc.quote_mint, + &sc.payer, + None, + ) + .unwrap(); mint_tokens_to_token_account( &mut sc.svm, &sc.quote_mint, "e_ata, TRADER_STARTING_BALANCE, &sc.authority, + None, ) .unwrap(); let init_ix = build_initialize_market_user_ix(sc, &keypair.pubkey()); - send_transaction_from_instructions(&mut sc.svm, vec![init_ix], &[&keypair], &keypair.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![init_ix], + &[&keypair], + &keypair.pubkey(), + None, + ) + .unwrap(); let market_user = market_user_pda(&sc.program_id, &sc.market, &keypair.pubkey()); Trader { keypair, @@ -2655,6 +2794,7 @@ fn fill_bid_side(sc: &mut Scenario) -> Vec { vec![ix], &[&filler.keypair], &filler.keypair.pubkey(), + None, ) .unwrap_or_else(|error| panic!("bid {order_id} should rest: {error:?}")); } @@ -2686,6 +2826,7 @@ fn place_bid( vec![ix], &[&trader.keypair], &trader.keypair.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -2814,6 +2955,7 @@ fn evicted_maker_settles_their_refund() { vec![settle_ix], &[&evicted.keypair], &evicted.keypair.pubkey(), + None, ) .unwrap(); @@ -2917,6 +3059,7 @@ fn trader_can_evict_their_own_worst_order() { vec![ix], &[&owner.keypair], &owner.keypair.pubkey(), + None, ) .unwrap(); @@ -2982,7 +3125,7 @@ fn send_market_switch( instruction: Instruction, signer: &Keypair, ) -> Result<(), String> { - send_transaction_from_instructions(svm, vec![instruction], &[signer], &signer.pubkey()) + send_transaction_from_instructions(svm, vec![instruction], &[signer], &signer.pubkey(), None) .map_err(|error| format!("{error:?}")) } @@ -3000,8 +3143,14 @@ fn place_pause_ask(sc: &mut Scenario) -> Result<(), String> { PAUSE_PRICE, PAUSE_QUANTITY, ); - send_transaction_from_instructions(&mut sc.svm, vec![ix], &[&sc.seller], &sc.seller.pubkey()) - .map_err(|error| format!("{error:?}")) + send_transaction_from_instructions( + &mut sc.svm, + vec![ix], + &[&sc.seller], + &sc.seller.pubkey(), + None, + ) + .map_err(|error| format!("{error:?}")) } #[test] @@ -3062,6 +3211,7 @@ fn paused_market_still_cancels_and_settles_a_resting_order() { vec![cancel_ix, settle_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); @@ -3090,6 +3240,7 @@ fn paused_market_still_pays_out_fills_and_withdraws_fees() { &sc.authority.pubkey(), &sc.quote_mint, &sc.payer, + None, ) .unwrap(); @@ -3108,8 +3259,14 @@ fn paused_market_still_pays_out_fills_and_withdraws_fees() { PAUSE_QUANTITY, &[(PAUSE_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![bid_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); let expected_fee = fee_ceil(PAUSE_GROSS); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault).unwrap(), @@ -3132,6 +3289,7 @@ fn paused_market_still_pays_out_fills_and_withdraws_fees() { vec![settle_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); assert_eq!( @@ -3146,6 +3304,7 @@ fn paused_market_still_pays_out_fills_and_withdraws_fees() { vec![withdraw_ix], &[&sc.authority], &sc.authority.pubkey(), + None, ) .unwrap(); assert_eq!( @@ -3232,6 +3391,7 @@ fn only_the_market_authority_can_pause_or_resume() { vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) .expect_err("a paused market must not take an order"); @@ -3279,6 +3439,7 @@ fn transaction_fee(svm: &mut LiteSVM, wallet: &Keypair) -> u64 { vec![system_instruction::transfer(&address, &address, 0)], &[wallet], &address, + None, ) .unwrap(); before - get_sol_balance(svm, &address) @@ -3302,6 +3463,7 @@ fn place_close_ask(sc: &mut Scenario) { vec![ask_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); } @@ -3322,8 +3484,14 @@ fn cross_close_ask(sc: &mut Scenario, quantity: u64) { quantity, &[(CLOSE_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![bid_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + None, + ) + .unwrap(); } /// The seller cancels their ask, which credits its locked base to their @@ -3336,6 +3504,7 @@ fn cancel_close_ask(sc: &mut Scenario) { vec![cancel_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); } @@ -3354,6 +3523,7 @@ fn settle_seller(sc: &mut Scenario) { vec![settle_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); } @@ -3371,7 +3541,8 @@ fn assert_close_returns_rent( let fee = transaction_fee(svm, owner); let before = get_sol_balance(svm, &owner.pubkey()); - send_transaction_from_instructions(svm, vec![close_ix], &[owner], &owner.pubkey()).unwrap(); + send_transaction_from_instructions(svm, vec![close_ix], &[owner], &owner.pubkey(), None) + .unwrap(); assert!(!account_is_open(svm, account)); assert_eq!(get_sol_balance(svm, &owner.pubkey()), before + rent - fee); @@ -3520,6 +3691,7 @@ fn close_market_user_returns_rent_when_nothing_is_open_or_owed() { vec![reinit_ix], &[&sc.seller], &sc.seller.pubkey(), + None, ) .unwrap(); assert!(account_is_open(&sc.svm, &sc.seller_market_user)); @@ -3623,6 +3795,7 @@ fn evicted_order_and_its_owners_market_user_close_after_settling() { vec![cancel_ix], &[&evicted.keypair], &evicted.keypair.pubkey(), + None, ) .unwrap(); } @@ -3638,6 +3811,7 @@ fn evicted_order_and_its_owners_market_user_close_after_settling() { vec![settle_ix], &[&evicted.keypair], &evicted.keypair.pubkey(), + None, ) .unwrap(); assert_eq!(read_open_order_count(&sc.svm, &evicted.market_user), 0); diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs b/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/order-book/anchor-v1/programs/order-book/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml index 230012008..4ec6c758c 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/Cargo.toml @@ -40,13 +40,11 @@ solana-sysvar = "3" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" # The LiteSVM tests load the compiled mock oracle program; depending on the # crate here lets the tests reuse its instruction-argument types and program ID. mock_price_feed = { path = "../mock-price-feed", features = ["no-entrypoint"] } -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs index f664cf698..fddda692c 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ error::ErrorCode as AnchorErrorCode, @@ -23,6 +19,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -164,7 +161,7 @@ impl Market { let payer = create_wallet(&mut svm, 100_000_000_000).unwrap(); let admin = create_wallet(&mut svm, 100_000_000_000).unwrap(); - let collateral_mint = create_token_mint(&mut svm, &admin, DECIMALS, None).unwrap(); + let collateral_mint = create_token_mint(&mut svm, &admin, DECIMALS, None, None).unwrap(); // Create the mock oracle feed as a fresh account owned by the mock // program; the admin is its update authority. @@ -189,6 +186,7 @@ impl Market { vec![initialize_feed], &[&admin, &feed_keypair], &admin.pubkey(), + None, ) .unwrap(); let feed = feed_keypair.pubkey(); @@ -224,6 +222,7 @@ impl Market { vec![initialize_pool], &[&admin], &admin.pubkey(), + None, ) .map_err(|error| format!("{error:?}"))?; @@ -268,6 +267,7 @@ impl Market { vec![set_price], &[&self.admin], &self.admin.pubkey(), + None, ) .unwrap(); } @@ -330,6 +330,7 @@ impl Market { &liquidator.pubkey(), &self.collateral_mint, &self.payer, + None, ) .unwrap(); (liquidator, liquidator_collateral) @@ -362,6 +363,7 @@ impl Market { &trader.pubkey(), &self.collateral_mint, &self.payer, + None, ) .unwrap(); mint_tokens_to_token_account( @@ -370,6 +372,7 @@ impl Market { &token_account, amount, &self.admin, + None, ) .unwrap(); (trader, token_account) @@ -410,6 +413,7 @@ impl Market { vec![instruction], &[provider], &provider.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -449,6 +453,7 @@ impl Market { vec![instruction], &[provider], &provider.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -503,6 +508,7 @@ impl Market { vec![instruction], &[trader], &trader.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -537,6 +543,7 @@ impl Market { vec![instruction], &[trader], &trader.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -574,6 +581,7 @@ impl Market { vec![instruction], &[liquidator], &liquidator.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -600,6 +608,7 @@ impl Market { vec![instruction], &[authority], &authority.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -620,6 +629,7 @@ impl Market { vec![instruction], &[caller], &caller.pubkey(), + None, ) .map_err(|error| format!("{error:?}")) } @@ -1409,6 +1419,7 @@ fn test_liquidation_of_underwater_long() { &liquidator.pubkey(), &market.collateral_mint, &market.payer, + None, ) .unwrap(); @@ -1444,6 +1455,7 @@ fn test_healthy_position_cannot_be_liquidated() { &liquidator.pubkey(), &market.collateral_mint, &market.payer, + None, ) .unwrap(); @@ -1476,6 +1488,7 @@ fn test_collect_fees() { &admin.pubkey(), &market.collateral_mint, &market.payer, + None, ) .unwrap(); market.collect_fees(&admin).unwrap(); @@ -1515,6 +1528,7 @@ fn test_collect_fees_requires_authority() { &imposter.pubkey(), &market.collateral_mint, &market.payer, + None, ) .unwrap(); // The pool's `has_one = authority` constraint refuses the imposter, so diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml b/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml index 50f7a6bce..2d2bce56f 100644 --- a/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml +++ b/finance/prop-amm/anchor-v1/programs/prop-amm/Cargo.toml @@ -37,13 +37,11 @@ spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" # The LiteSVM tests load the compiled mock oracle program; depending on the # crate here lets the tests reuse its instruction-argument types and program ID. mock_price_feed = { path = "../mock-price-feed", features = ["no-entrypoint"] } -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs index 5b8df8dc6..9fdbc6e6a 100644 --- a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/test_prop_amm.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ error::{ErrorCode as AnchorErrorCode, ERROR_CODE_OFFSET}, @@ -18,6 +14,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -148,8 +145,8 @@ impl Market { let payer = create_wallet(&mut svm, 100_000_000_000).unwrap(); let operator = create_wallet(&mut svm, 100_000_000_000).unwrap(); - let base_mint = create_token_mint(&mut svm, &operator, NVDAX_DECIMALS, None).unwrap(); - let quote_mint = create_token_mint(&mut svm, &operator, USDC_DECIMALS, None).unwrap(); + let base_mint = create_token_mint(&mut svm, &operator, NVDAX_DECIMALS, None, None).unwrap(); + let quote_mint = create_token_mint(&mut svm, &operator, USDC_DECIMALS, None, None).unwrap(); // Create the mock oracle feed as a fresh account owned by the mock // program; the operator is its update authority. @@ -174,6 +171,7 @@ impl Market { vec![initialize_feed], &[&operator, &feed_keypair], &operator.pubkey(), + None, ) .unwrap(); let feed = feed_keypair.pubkey(); @@ -210,22 +208,29 @@ impl Market { vec![initialize_market], &[&operator], &operator.pubkey(), + None, ) .map_err(|error| format!("{error:?}"))?; // Fund the operator's inventory accounts. let operator_base = - create_associated_token_account(&mut svm, &operator.pubkey(), &base_mint, &payer) - .unwrap(); - let operator_quote = - create_associated_token_account(&mut svm, &operator.pubkey(), "e_mint, &payer) + create_associated_token_account(&mut svm, &operator.pubkey(), &base_mint, &payer, None) .unwrap(); + let operator_quote = create_associated_token_account( + &mut svm, + &operator.pubkey(), + "e_mint, + &payer, + None, + ) + .unwrap(); mint_tokens_to_token_account( &mut svm, &base_mint, &operator_base, 10_000 * ONE_NVDAX, &operator, + None, ) .unwrap(); mint_tokens_to_token_account( @@ -234,6 +239,7 @@ impl Market { &operator_quote, 10_000_000 * ONE_USDC, &operator, + None, ) .unwrap(); @@ -285,6 +291,7 @@ impl Market { vec![set_price], &[&self.operator], &self.operator.pubkey(), + None, ) .unwrap(); } @@ -316,6 +323,7 @@ impl Market { &trader.pubkey(), &self.base_mint, &self.payer, + None, ) .unwrap(); let quote_account = create_associated_token_account( @@ -323,6 +331,7 @@ impl Market { &trader.pubkey(), &self.quote_mint, &self.payer, + None, ) .unwrap(); if base > 0 { @@ -332,6 +341,7 @@ impl Market { &base_account, base, &self.operator, + None, ) .unwrap(); } @@ -342,6 +352,7 @@ impl Market { "e_account, quote, &self.operator, + None, ) .unwrap(); } @@ -407,6 +418,7 @@ impl Market { vec![instruction], &[signer], &signer.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -442,6 +454,7 @@ impl Market { vec![instruction], &[signer], &signer.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -490,6 +503,7 @@ impl Market { vec![instruction], &[trader], &trader.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -526,6 +540,7 @@ impl Market { vec![instruction], &[&payer, signer], &payer.pubkey(), + None, ) .map(|_| ()) .map_err(|error| format!("{error:?}")) @@ -572,6 +587,7 @@ impl Market { vec![transfer], &[sender], &sender.pubkey(), + None, ) .expect("a plain token transfer into a vault should succeed"); } diff --git a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs b/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/prop-amm/anchor-v1/programs/prop-amm/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml b/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml index 4a821785f..e01473b69 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml +++ b/finance/token-swap/anchor-v1/programs/token-swap/Cargo.toml @@ -30,10 +30,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata", "spl-token-interface"] litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs b/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs index 1330d95b2..507cb39ae 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs +++ b/finance/token-swap/anchor-v1/programs/token-swap/tests/test_swap.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -12,6 +8,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -71,8 +68,8 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { /// Ensure mint_a < mint_b by pubkey ordering (the program may require this). fn ordered_mints(svm: &mut LiteSVM, authority: &Keypair, decimals: u8) -> (Pubkey, Pubkey) { loop { - let a = create_token_mint(svm, authority, decimals, None).unwrap(); - let b = create_token_mint(svm, authority, decimals, None).unwrap(); + let a = create_token_mint(svm, authority, decimals, None, None).unwrap(); + let b = create_token_mint(svm, authority, decimals, None, None).unwrap(); if a.as_ref() < b.as_ref() { return (a, b); } @@ -148,14 +145,28 @@ fn setup_config_and_mints() -> TestSetup { // Create ATAs for admin and mint tokens let holder_account_a = - create_associated_token_account(&mut svm, &admin.pubkey(), &mint_a, &payer).unwrap(); + create_associated_token_account(&mut svm, &admin.pubkey(), &mint_a, &payer, None).unwrap(); let holder_account_b = - create_associated_token_account(&mut svm, &admin.pubkey(), &mint_b, &payer).unwrap(); + create_associated_token_account(&mut svm, &admin.pubkey(), &mint_b, &payer, None).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_a, &holder_account_a, minted_amount, &admin) - .unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_b, &holder_account_b, minted_amount, &admin) - .unwrap(); + mint_tokens_to_token_account( + &mut svm, + &mint_a, + &holder_account_a, + minted_amount, + &admin, + None, + ) + .unwrap(); + mint_tokens_to_token_account( + &mut svm, + &mint_b, + &holder_account_b, + minted_amount, + &admin, + None, + ) + .unwrap(); // Create AMM let initialize_config_ix = Instruction::new_with_bytes( @@ -178,6 +189,7 @@ fn setup_config_and_mints() -> TestSetup { vec![initialize_config_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); @@ -233,6 +245,7 @@ fn send_initialize_pool(ts: &mut TestSetup, amount_a: u64, amount_b: u64) -> Res vec![ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .map(|_| ()) .map_err(|e| format!("{e:?}")) @@ -278,6 +291,7 @@ fn test_initialize_config() { vec![initialize_config_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); @@ -403,13 +417,15 @@ struct Depositor { fn fund_depositor(ts: &mut TestSetup, amount_a: u64, amount_b: u64) -> Depositor { let wallet = create_wallet(&mut ts.svm, 10_000_000_000).unwrap(); let token_a = - create_associated_token_account(&mut ts.svm, &wallet.pubkey(), &ts.mint_a, &ts.payer) + create_associated_token_account(&mut ts.svm, &wallet.pubkey(), &ts.mint_a, &ts.payer, None) .unwrap(); let token_b = - create_associated_token_account(&mut ts.svm, &wallet.pubkey(), &ts.mint_b, &ts.payer) + create_associated_token_account(&mut ts.svm, &wallet.pubkey(), &ts.mint_b, &ts.payer, None) .unwrap(); - mint_tokens_to_token_account(&mut ts.svm, &ts.mint_a, &token_a, amount_a, &ts.admin).unwrap(); - mint_tokens_to_token_account(&mut ts.svm, &ts.mint_b, &token_b, amount_b, &ts.admin).unwrap(); + mint_tokens_to_token_account(&mut ts.svm, &ts.mint_a, &token_a, amount_a, &ts.admin, None) + .unwrap(); + mint_tokens_to_token_account(&mut ts.svm, &ts.mint_b, &token_b, amount_b, &ts.admin, None) + .unwrap(); let lp_token = derive_ata(&wallet.pubkey(), &ts.liquidity_provider_mint); Depositor { wallet, @@ -458,6 +474,7 @@ fn send_deposit_from( vec![ix], &[&ts.payer, &depositor.wallet], &ts.payer.pubkey(), + None, ) .map(|_| ()) .map_err(|e| format!("{e:?}")) @@ -488,6 +505,7 @@ fn test_pool_creation_cannot_be_front_run() { &ts.holder_account_a, 300 * one, &ts.admin, + None, ) .unwrap(); mint_tokens_to_token_account( @@ -496,6 +514,7 @@ fn test_pool_creation_cannot_be_front_run() { &ts.holder_account_b, 800 * one, &ts.admin, + None, ) .unwrap(); send_initialize_pool(&mut ts, 400 * one, 900 * one).expect("Maria opens the pool"); @@ -551,6 +570,7 @@ fn test_pool_creation_cannot_be_front_run() { &mallory.token_b, 100 * one, &ts.admin, + None, ) .unwrap(); assert_error_code( @@ -597,6 +617,7 @@ fn test_swap_a_to_b() { vec![swap_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .unwrap(); @@ -650,6 +671,7 @@ fn test_withdraw_liquidity() { vec![withdraw_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .unwrap(); @@ -711,6 +733,7 @@ fn swap_a_to_b(ts: &mut TestSetup, input_amount: u64) { vec![swap_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .unwrap(); } @@ -741,6 +764,7 @@ fn test_claim_admin_fees() { vec![claim_ix_first], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .unwrap(); @@ -782,6 +806,7 @@ fn test_claim_admin_fees() { vec![claim_ix_second], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .unwrap(); @@ -821,6 +846,7 @@ fn test_claim_admin_fees() { vec![claim_ix_again], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ); assert_error_code(result, "NothingToClaim"); @@ -842,12 +868,22 @@ fn test_claim_admin_fees_rejects_non_admin() { // Create a non-admin actor with their own ATAs and try to claim. let attacker = create_wallet(&mut ts.svm, 100_000_000_000).unwrap(); - let attacker_token_a = - create_associated_token_account(&mut ts.svm, &attacker.pubkey(), &ts.mint_a, &ts.payer) - .unwrap(); - let attacker_token_b = - create_associated_token_account(&mut ts.svm, &attacker.pubkey(), &ts.mint_b, &ts.payer) - .unwrap(); + let attacker_token_a = create_associated_token_account( + &mut ts.svm, + &attacker.pubkey(), + &ts.mint_a, + &ts.payer, + None, + ) + .unwrap(); + let attacker_token_b = create_associated_token_account( + &mut ts.svm, + &attacker.pubkey(), + &ts.mint_b, + &ts.payer, + None, + ) + .unwrap(); let claim_ix = Instruction::new_with_bytes( ts.program_id, @@ -874,6 +910,7 @@ fn test_claim_admin_fees_rejects_non_admin() { vec![claim_ix], &[&ts.payer, &attacker], &ts.payer.pubkey(), + None, ); assert_error_code(result, "ConstraintHasOne"); } @@ -924,6 +961,7 @@ fn send_deposit(ts: &mut TestSetup, amount_a: u64, amount_b: u64) -> Result<(), vec![ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .map(|_| ()) .map_err(|e| format!("{e:?}")) @@ -1080,6 +1118,7 @@ fn test_deposit_after_swap_uses_shifted_effective_ratio() { vec![swap_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("swap a→b"); @@ -1199,6 +1238,7 @@ fn test_lp_mint_proportional_to_share_of_pool() { vec![withdraw], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("withdraw the second deposit's LP"); let pool_a_after = get_token_account_balance(&ts.svm, &ts.pool_a).unwrap(); @@ -1288,8 +1328,24 @@ fn test_donation_cannot_round_a_later_deposit_to_zero() { // The donation: tokens sent straight to the vaults, no LP minted. let donation: u64 = 1_000_000; - mint_tokens_to_token_account(&mut ts.svm, &ts.mint_a, &ts.pool_a, donation, &ts.admin).unwrap(); - mint_tokens_to_token_account(&mut ts.svm, &ts.mint_b, &ts.pool_b, donation, &ts.admin).unwrap(); + mint_tokens_to_token_account( + &mut ts.svm, + &ts.mint_a, + &ts.pool_a, + donation, + &ts.admin, + None, + ) + .unwrap(); + mint_tokens_to_token_account( + &mut ts.svm, + &ts.mint_b, + &ts.pool_b, + donation, + &ts.admin, + None, + ) + .unwrap(); // The victim deposits exactly as much as was donated. Dividing by the // bare supply of 1 would mint 1_000_000 * 1 / 1_000_101 = 0 LP tokens. @@ -1312,6 +1368,7 @@ fn test_donation_cannot_round_a_later_deposit_to_zero() { vec![attacker_exit], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("attacker withdraws"); let attacker_a_back = @@ -1340,6 +1397,7 @@ fn test_deposit_after_every_lp_token_is_burned() { vec![withdraw_all], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("withdraw everything"); assert_eq!(get_token_account_balance(&ts.svm, &ts.pool_a).unwrap(), 100); @@ -1473,6 +1531,7 @@ fn test_swap_reverts_when_output_below_min() { vec![baseline_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("baseline swap should succeed"); let after_b = get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); @@ -1490,6 +1549,7 @@ fn test_swap_reverts_when_output_below_min() { vec![strict_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ); assert_error_code(result, "SlippageExceeded"); } @@ -1521,6 +1581,7 @@ fn test_deposit_reverts_when_lp_below_min() { vec![strict_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ); assert_error_code(result, "DepositBelowMinimum"); @@ -1531,6 +1592,7 @@ fn test_deposit_reverts_when_lp_below_min() { vec![ok_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("deposit at exact LP floor should succeed"); } @@ -1552,6 +1614,7 @@ fn test_withdraw_reverts_when_below_min() { vec![strict_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ); assert_error_code(result, "WithdrawalBelowMinimum"); @@ -1562,6 +1625,7 @@ fn test_withdraw_reverts_when_below_min() { vec![strict_ix_b], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ); assert_error_code(result_b, "WithdrawalBelowMinimum"); } @@ -1580,6 +1644,7 @@ fn test_swap_with_zero_min_output_still_succeeds() { vec![ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("swap with min_output_amount=0 must succeed"); let after_b = get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); @@ -1638,6 +1703,7 @@ fn test_swap_b_to_a_trader_pays_full_input() { vec![swap_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("B→A swap should succeed"); @@ -1685,6 +1751,7 @@ fn test_invariant_holds_after_b_to_a_swap() { vec![swap_ix], &[&ts.payer, &ts.admin], &ts.payer.pubkey(), + None, ) .expect("B→A swap should succeed"); diff --git a/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs b/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/finance/token-swap/anchor-v1/programs/token-swap/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml b/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml index 92bb99daf..699667e08 100644 --- a/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml +++ b/tokens/create-token/anchor-v1/programs/create-token/Cargo.toml @@ -28,10 +28,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs b/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs index 631565511..ada4a578b 100644 --- a/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs +++ b/tokens/create-token/anchor-v1/programs/create-token/tests/test_create_token.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::create_wallet, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -87,6 +83,7 @@ fn test_create_spl_token() { vec![instruction], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -141,6 +138,7 @@ fn test_create_nft() { vec![instruction], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); diff --git a/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs b/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/create-token/anchor-v1/programs/create-token/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml index cf6534329..7d7296754 100644 --- a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml +++ b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/Cargo.toml @@ -32,10 +32,8 @@ libsecp256k1 = "0.7.2" litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs index a6128f9a0..67f363653 100644 --- a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs +++ b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/test_external_delegate.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -14,6 +10,7 @@ use { solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -126,6 +123,7 @@ fn initialize_user_account( vec![init_instruction], &[authority, user_account], &authority.pubkey(), + None, ) .unwrap(); } @@ -152,6 +150,7 @@ fn set_ethereum_address( vec![set_address_instruction], &[authority], &authority.pubkey(), + None, ) .unwrap(); } @@ -186,21 +185,23 @@ fn setup_transfer_fixture() -> TransferFixture { let (user_pda, _bump) = Pubkey::find_program_address(&[user_account.as_ref()], &program_id); - let mint = create_token_mint(&mut svm, &authority, MINT_DECIMALS, None).unwrap(); + let mint = create_token_mint(&mut svm, &authority, MINT_DECIMALS, None, None).unwrap(); let user_pda_token_account = - create_associated_token_account(&mut svm, &user_pda, &mint, &authority).unwrap(); + create_associated_token_account(&mut svm, &user_pda, &mint, &authority, None).unwrap(); mint_tokens_to_token_account( &mut svm, &mint, &user_pda_token_account, MINT_AMOUNT, &authority, + None, ) .unwrap(); let recipient = Keypair::new(); let recipient_token_account = - create_associated_token_account(&mut svm, &recipient.pubkey(), &mint, &authority).unwrap(); + create_associated_token_account(&mut svm, &recipient.pubkey(), &mint, &authority, None) + .unwrap(); TransferFixture { svm, @@ -294,6 +295,7 @@ fn test_transfer_tokens_with_valid_signature_moves_tokens_and_increments_nonce() vec![transfer_instruction], &[&fixture.authority], &authority_pubkey, + None, ) .unwrap(); @@ -337,6 +339,7 @@ fn test_transfer_tokens_replayed_signature_fails() { vec![transfer_instruction.clone()], &[&fixture.authority], &authority_pubkey, + None, ) .unwrap(); @@ -348,6 +351,7 @@ fn test_transfer_tokens_replayed_signature_fails() { vec![transfer_instruction], &[&fixture.authority], &authority_pubkey, + None, ); assert!( replay_result.is_err(), @@ -393,6 +397,7 @@ fn test_transfer_tokens_signature_over_different_amount_fails() { vec![transfer_instruction], &[&fixture.authority], &authority_pubkey, + None, ); assert!( result.is_err(), @@ -420,6 +425,7 @@ fn test_transfer_tokens_signature_over_different_recipient_fails() { &attacker.pubkey(), &fixture.mint, &fixture.authority, + None, ) .unwrap(); @@ -445,6 +451,7 @@ fn test_transfer_tokens_signature_over_different_recipient_fails() { vec![transfer_instruction], &[&fixture.authority], &authority_pubkey, + None, ); assert!( result.is_err(), @@ -485,6 +492,7 @@ fn test_transfer_tokens_wrong_solana_authority_fails() { vec![transfer_instruction], &[&mallory], &mallory_pubkey, + None, ); assert!( result.is_err(), @@ -523,6 +531,7 @@ fn test_authority_transfer() { vec![authority_transfer_instruction], &[&fixture.authority], &fixture.authority.pubkey(), + None, ) .unwrap(); diff --git a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs b/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/external-delegate-token-master/anchor-v1/programs/external-delegate-token-master/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml b/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml index 144ec9565..23e13dcf1 100644 --- a/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml +++ b/tokens/nft-minter/anchor-v1/programs/nft-minter/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs index 923e9ee70..c39968123 100644 --- a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs +++ b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/test_nft_minter.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance}, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -120,6 +116,7 @@ fn test_mint_nft() { vec![instruction], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); diff --git a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs b/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/nft-minter/anchor-v1/programs/nft-minter/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml b/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml index 4d412648a..d03e0e104 100644 --- a/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml +++ b/tokens/nft-operations/anchor-v1/programs/mint-nft/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs index e3d4db767..363481fb1 100644 --- a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs +++ b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/test_nft_operations.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance}, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -131,6 +127,7 @@ fn test_create_collection() { vec![instruction], &[&payer, &collection_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -199,6 +196,7 @@ fn test_mint_nft_to_collection() { vec![create_collection_ix], &[&payer, &collection_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -238,6 +236,7 @@ fn test_mint_nft_to_collection() { vec![mint_nft_ix], &[&payer, &nft_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -296,6 +295,7 @@ fn test_verify_collection() { vec![create_collection_ix], &[&payer, &collection_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -335,6 +335,7 @@ fn test_verify_collection() { vec![mint_nft_ix], &[&payer, &nft_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -358,7 +359,7 @@ fn test_verify_collection() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![verify_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![verify_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify the metadata still exists after verification diff --git a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs b/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/nft-operations/anchor-v1/programs/mint-nft/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml index f32cc0120..d1d28f4c8 100644 --- a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml +++ b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs index b6ff2deeb..6afb6c67b 100644 --- a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs +++ b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/test_pda_mint.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance}, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -107,7 +103,7 @@ fn test_create_token_and_mint() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify mint created @@ -140,7 +136,7 @@ fn test_create_token_and_mint() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify 100 tokens minted (in minor units) diff --git a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs b/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/pda-mint-authority/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml b/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml index 12c81f3bc..6fbc328f5 100644 --- a/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml +++ b/tokens/token-extensions/basics/anchor-v1/programs/basics/Cargo.toml @@ -25,12 +25,10 @@ anchor-lang = { version = "1.2.0", features= ["init-if-needed"]} [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs index 49e2faa86..292d301cf 100644 --- a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs +++ b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/test_basics.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, + assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{get_token_extensions_account_address, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, @@ -65,8 +61,14 @@ fn test_create_token_and_mint_and_transfer() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_token_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![create_token_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify mint account exists let mint_account = svm.get_account(&mint).expect("Mint account should exist"); @@ -91,8 +93,14 @@ fn test_create_token_and_mint_and_transfer() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_ata_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![create_ata_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify ATA exists let ata_account = svm.get_account(&payer_ata).expect("Payer ATA should exist"); @@ -118,8 +126,14 @@ fn test_create_token_and_mint_and_transfer() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![mint_token_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![mint_token_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); assert_token_account_balance( &svm, @@ -155,8 +169,14 @@ fn test_create_token_and_mint_and_transfer() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); assert_token_account_balance( &svm, diff --git a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs b/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/basics/anchor-v1/programs/basics/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml index 35360c239..a2a3408a7 100644 --- a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml +++ b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/Cargo.toml @@ -25,12 +25,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs index a4e858d69..f5117dda2 100644 --- a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs +++ b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/test_cpi_guard.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, + assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_mint, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, @@ -119,7 +115,7 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { let token_keypair = Keypair::new(); // Step 1: Create a Token Extensions mint (no extensions needed on the mint itself) - let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[]).unwrap(); + let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[], None).unwrap(); svm.expire_blockhash(); // Step 2: Create basic token account (needs explicit keypair for reallocate flow) @@ -134,6 +130,7 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { token_ixs, &[&payer, &token_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -146,19 +143,32 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { &payer.pubkey(), &[cpi_guard_extension_type], ); - send_transaction_from_instructions(&mut svm, vec![reallocate_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![reallocate_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Enable CPI Guard let enable_ix = enable_cpi_guard_instruction(&token_keypair.pubkey(), &payer.pubkey()); - send_transaction_from_instructions(&mut svm, vec![enable_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![enable_ix], &[&payer], &payer.pubkey(), None) .unwrap(); svm.expire_blockhash(); // Step 5: Mint 1 token to the token account - mint_tokens_to_token_extensions_account(&mut svm, &mint, &token_keypair.pubkey(), 1, &payer) - .unwrap(); + mint_tokens_to_token_extensions_account( + &mut svm, + &mint, + &token_keypair.pubkey(), + 1, + &payer, + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 6: Try CPI transfer - should fail because CPI Guard is enabled @@ -178,8 +188,13 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { .to_account_metas(None), ); - let result = - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![transfer_ix], + &[&payer], + &payer.pubkey(), + None, + ); assert!( result.is_err(), "Transfer should fail when CPI Guard is enabled" @@ -188,8 +203,14 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { // Step 7: Disable CPI Guard let disable_ix = disable_cpi_guard_instruction(&token_keypair.pubkey(), &payer.pubkey()); - send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![disable_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 8: Transfer should now succeed @@ -207,8 +228,14 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix2], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); assert_token_account_balance( &svm, diff --git a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs b/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/cpi-guard/anchor-v1/programs/cpi-guard/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml index d6fd1c65c..463e15e6a 100644 --- a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml +++ b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/Cargo.toml @@ -25,12 +25,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs index e3c5ebecf..34359e9e5 100644 --- a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs +++ b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/test_default_account_state.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, + assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, @@ -84,6 +80,7 @@ fn test_default_account_state() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -105,6 +102,7 @@ fn test_default_account_state() { create_token1_ixs, &[&payer, &token1], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -123,6 +121,7 @@ fn test_default_account_state() { &token1.pubkey(), 1, &payer, + None, ); assert!(result.is_err(), "Minting to a frozen account should fail"); svm.expire_blockhash(); @@ -142,7 +141,7 @@ fn test_default_account_state() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey(), None) .unwrap(); svm.expire_blockhash(); @@ -159,6 +158,7 @@ fn test_default_account_state() { create_token2_ixs, &[&payer, &token2], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -177,6 +177,7 @@ fn test_default_account_state() { &token2.pubkey(), 1, &payer, + None, ) .unwrap(); diff --git a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs b/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/default-account-state/anchor-v1/programs/default-account-state/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml b/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml index 44ba9db14..8e8be6c17 100644 --- a/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml +++ b/tokens/token-extensions/group/anchor-v1/programs/group/Cargo.toml @@ -25,12 +25,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs b/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs index 6c807f363..e675c312e 100644 --- a/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs +++ b/tokens/token-extensions/group/anchor-v1/programs/group/tests/test_group.rs @@ -1,14 +1,13 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, + }, solana_signer::Signer, }; @@ -37,8 +36,14 @@ fn test_initialize_group() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![instruction], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify mint was created with group pointer extension let mint_data = svm diff --git a/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs b/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/group/anchor-v1/programs/group/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml index 9f8fefb57..a398f45b1 100644 --- a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml +++ b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/Cargo.toml @@ -25,12 +25,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs index 9cf895769..301f36a57 100644 --- a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs +++ b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/test_immutable_owner.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{create_token_extensions_mint, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, @@ -65,7 +61,7 @@ fn test_create_token_account_with_immutable_owner() { let token_keypair = Keypair::new(); // Step 1: Create a Token Extensions mint with 2 decimals (no extensions needed on mint) - let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[]).unwrap(); + let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[], None).unwrap(); svm.expire_blockhash(); // Step 2: Call program to create token account with ImmutableOwner extension @@ -86,6 +82,7 @@ fn test_create_token_account_with_immutable_owner() { vec![initialize_ix], &[&payer, &token_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -113,6 +110,7 @@ fn test_create_token_account_with_immutable_owner() { vec![set_authority_ix], &[&payer], &payer.pubkey(), + None, ); assert!( result.is_err(), diff --git a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs b/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/immutable-owner/anchor-v1/programs/immutable-owner/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml index 624099607..1c8374fd6 100644 --- a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml +++ b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/Cargo.toml @@ -26,12 +26,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs index 0e3da857c..63013f92d 100644 --- a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs +++ b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/test_interest_bearing.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,10 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, + }, solana_signer::Signer, }; @@ -46,6 +45,7 @@ fn test_initialize_and_update_rate() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -69,8 +69,14 @@ fn test_initialize_and_update_rate() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_rate_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![update_rate_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify mint still exists after rate update let mint_account = svm diff --git a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs b/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/interest-bearing/anchor-v1/programs/interest-bearing/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml index 5d551b786..aaf413bcd 100644 --- a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml +++ b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/Cargo.toml @@ -25,12 +25,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs index 19af4a044..69a38f8c0 100644 --- a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs +++ b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/test_memo_transfer.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, + assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_mint, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, @@ -116,7 +112,7 @@ fn test_memo_transfer() { let token_keypair = Keypair::new(); // Step 1: Create a standard Token Extensions mint (no extensions on the mint) - let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[]).unwrap(); + let mint = create_token_extensions_mint(&mut svm, &payer, 2, None, &[], None).unwrap(); svm.expire_blockhash(); // Step 2: Create token account with RequiredMemo extension via program @@ -137,6 +133,7 @@ fn test_memo_transfer() { vec![initialize_ix], &[&payer, &token_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -164,12 +161,20 @@ fn test_memo_transfer() { create_source_ixs, &[&payer, &source_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_keypair.pubkey(), 100, &payer) - .unwrap(); + mint_tokens_to_token_extensions_account( + &mut svm, + &mint, + &source_keypair.pubkey(), + 100, + &payer, + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer without memo - should fail @@ -179,8 +184,13 @@ fn test_memo_transfer() { &payer.pubkey(), 1, ); - let result = - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![transfer_ix], + &[&payer], + &payer.pubkey(), + None, + ); assert!(result.is_err(), "Transfer without memo should fail"); svm.expire_blockhash(); @@ -197,6 +207,7 @@ fn test_memo_transfer() { vec![memo_ix, transfer_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -219,8 +230,14 @@ fn test_memo_transfer() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![disable_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 7: Transfer without memo should now succeed (memo disabled) @@ -236,6 +253,7 @@ fn test_memo_transfer() { create_source2_ixs, &[&payer, &source2_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -246,6 +264,7 @@ fn test_memo_transfer() { &source2_keypair.pubkey(), 100, &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -256,8 +275,14 @@ fn test_memo_transfer() { &payer.pubkey(), 1, ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix2], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); assert_token_account_balance( &svm, diff --git a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs b/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/memo-transfer/anchor-v1/programs/memo-transfer/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml index 3991d215d..f7aebdecd 100644 --- a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml +++ b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/Cargo.toml @@ -27,12 +27,10 @@ spl-type-length-value = "0.9.1" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs index 8436cbe86..ac301ccfe 100644 --- a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs +++ b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/test_metadata.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,10 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, + }, solana_signer::Signer, }; @@ -53,6 +52,7 @@ fn test_metadata_full_flow() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -82,8 +82,14 @@ fn test_metadata_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_name_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![update_name_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Add custom field @@ -109,6 +115,7 @@ fn test_metadata_full_flow() { vec![add_custom_field_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -128,8 +135,14 @@ fn test_metadata_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![remove_key_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![remove_key_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Update authority to None @@ -150,6 +163,7 @@ fn test_metadata_full_flow() { vec![update_authority_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -164,7 +178,7 @@ fn test_metadata_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![emit_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![emit_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify mint still exists after all operations diff --git a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs b/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/metadata/anchor-v1/programs/metadata/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml index 7b2f615f3..f3eea6484 100644 --- a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml +++ b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/Cargo.toml @@ -22,9 +22,7 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs index 344e6b54d..3b0b801fc 100644 --- a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs +++ b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/test_mint_close_authority.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,10 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, + }, solana_signer::Signer, }; @@ -46,6 +45,7 @@ fn test_create_and_close_mint() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -68,7 +68,7 @@ fn test_create_and_close_mint() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify mint no longer exists (lamports returned to authority) @@ -94,6 +94,7 @@ fn test_create_and_close_mint() { vec![initialize_ix2], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -124,8 +125,14 @@ fn test_create_and_close_mint() { ], data: vec![9], // CloseAccount }; - send_transaction_from_instructions(&mut svm, vec![close_direct_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![close_direct_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify mint is closed again let mint_account = svm.get_account(&mint_keypair.pubkey()); diff --git a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs b/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/mint-close-authority/anchor-v1/programs/mint-close-authority/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml index 1e6d79e4e..80e0fc185 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/Cargo.toml @@ -43,9 +43,7 @@ solana-keypair = "3.0.1" solana-signer = "3.0.0" solana-instruction = "3.0.0" solana-pubkey = "3.0.0" -solana-kite = "0.5.0" -solana-message = "4.2.4" -solana-transaction = "4.1.5" +solana-kite = "0.6.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs index 36df89dd5..9f2ef9df9 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/test_extension_nft.rs @@ -19,10 +19,6 @@ //! (the crate constant) rather than a hardcoded literal, so the test keeps //! working after the id is regenerated. -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ prelude::Pubkey, solana_program::system_program, InstructionData, ToAccountMetas, @@ -30,7 +26,7 @@ use { litesvm::LiteSVM, solana_instruction::Instruction, solana_keypair::Keypair, - solana_kite::{create_wallet, get_pda_and_bump, Seed}, + solana_kite::{create_wallet, get_pda_and_bump, send_transaction_from_instructions, Seed}, solana_signer::Signer, }; @@ -173,6 +169,7 @@ fn test_init_player_mint_and_chop() { vec![init_player_ix(&program_id, &signer)], &[&payer], &signer, + None, ) .expect("init_player should succeed"); @@ -192,6 +189,7 @@ fn test_init_player_mint_and_chop() { vec![mint_nft_ix(&program_id, &signer, &mint.pubkey())], &[&payer, &mint], &signer, + None, ) .expect("mint_nft should succeed"); @@ -223,6 +221,7 @@ fn test_init_player_mint_and_chop() { vec![chop_tree_ix(&program_id, &signer, &mint.pubkey(), 1)], &[&payer], &signer, + None, ) .expect("chop_tree should succeed"); diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor-v1/programs/extension_nft/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml index a4aef0977..4f92bf905 100644 --- a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml +++ b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/Cargo.toml @@ -22,9 +22,7 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs index 5c12d518f..42a1b401a 100644 --- a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs +++ b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/test_non_transferable.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -53,6 +49,7 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -69,9 +66,14 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { // Step 2: Create ATAs for sender and recipient let recipient = Keypair::new(); - let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) - .unwrap(); + let source_ata = create_token_extensions_account( + &mut svm, + &payer.pubkey(), + &mint_keypair.pubkey(), + &payer, + None, + ) + .unwrap(); svm.expire_blockhash(); let dest_ata = create_token_extensions_account( @@ -79,6 +81,7 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { &recipient.pubkey(), &mint_keypair.pubkey(), &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -90,6 +93,7 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { &source_ata, 1, &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -104,6 +108,7 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { 1, 2, // decimals &[], + None, ); assert!( result.is_err(), diff --git a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs b/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/non-transferable/anchor-v1/programs/non-transferable/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml index 25702cfd2..7f3c16923 100644 --- a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml +++ b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/Cargo.toml @@ -26,12 +26,10 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs index 72e90235c..3b427655a 100644 --- a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs +++ b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/test_permanent_delegate.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - assert_token_account_balance, create_wallet, + assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, solana_signer::Signer, @@ -106,6 +102,7 @@ fn test_create_mint_with_permanent_delegate_and_burn() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -124,6 +121,7 @@ fn test_create_mint_with_permanent_delegate_and_burn() { create_ata_ixs, &[&payer, &token_account], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -135,6 +133,7 @@ fn test_create_mint_with_permanent_delegate_and_burn() { &token_account.pubkey(), 100, &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -147,7 +146,7 @@ fn test_create_mint_with_permanent_delegate_and_burn() { 100, 2, // decimals ); - send_transaction_from_instructions(&mut svm, vec![burn_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![burn_ix], &[&payer], &payer.pubkey(), None) .unwrap(); assert_token_account_balance( diff --git a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs b/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/permanent-delegate/anchor-v1/programs/permanent-delegate/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml index 891b2326a..07ca360e6 100644 --- a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml +++ b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/Cargo.toml @@ -22,9 +22,7 @@ anchor-spl = "1.2.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs index 28a3ac4a8..55ae4fda3 100644 --- a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs +++ b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/test_transfer_fee.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{ @@ -14,7 +10,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, @@ -72,13 +68,20 @@ fn test_transfer_fee_full_flow() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); // Step 2: Create sender ATA and mint 300 tokens - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) - .unwrap(); + create_token_extensions_account( + &mut svm, + &payer.pubkey(), + &mint_keypair.pubkey(), + &payer, + None, + ) + .unwrap(); svm.expire_blockhash(); mint_tokens_to_token_extensions_account( @@ -87,6 +90,7 @@ fn test_transfer_fee_full_flow() { &sender_ata, 300, &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -107,8 +111,14 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer 200 tokens (fee = min(1% * 200 = 2, max_fee = 1) = 1, capped by maximumFee) @@ -127,8 +137,14 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix2], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Harvest transfer fees from recipient token account to mint @@ -142,8 +158,14 @@ fn test_transfer_fee_full_flow() { metas.push(AccountMeta::new(recipient_ata, false)); metas }); - send_transaction_from_instructions(&mut svm, vec![harvest_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![harvest_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 6: Withdraw harvested fees from mint to sender's token account @@ -158,8 +180,14 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![withdraw_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![withdraw_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 7: Update transfer fee to 0 @@ -177,6 +205,12 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_fee_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![update_fee_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); } diff --git a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-fee/anchor-v1/programs/transfer-fee/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml index ba44d62c8..a9dce9e2b 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/Cargo.toml @@ -28,12 +28,10 @@ spl-transfer-hook-interface = "0.9.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index a8a62e8bd..1d698c893 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -11,7 +7,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -73,6 +69,7 @@ fn test_transfer_hook_account_data_as_seed() { &[MintExtension::TransferHook { program_id: program_id, }], + None, ) .unwrap(); svm.expire_blockhash(); @@ -82,14 +79,16 @@ fn test_transfer_hook_account_data_as_seed() { // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer, None).unwrap(); svm.expire_blockhash(); let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) + .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) + .unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates counter PDA) @@ -107,8 +106,14 @@ fn test_transfer_hook_account_data_as_seed() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with hook @@ -131,6 +136,7 @@ fn test_transfer_hook_account_data_as_seed() { transfer_amount, decimals, &extra_accounts, + None, ) .unwrap(); svm.expire_blockhash(); @@ -158,6 +164,7 @@ fn test_transfer_hook_account_data_as_seed() { vec![direct_hook_ix], &[&payer], &payer.pubkey(), + None, ); assert!( result.is_err(), diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml index bc5ad66eb..edc8d6382 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/Cargo.toml @@ -38,12 +38,10 @@ spl-discriminator = "0.5.1" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs index 20bc2b97b..11eb906c6 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/test_abl_token.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,10 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, + }, solana_signer::Signer, }; @@ -47,8 +46,14 @@ fn test_init_config_and_init_mint() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_config_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_config_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 2: Initialize mint with transfer hook and metadata @@ -84,6 +89,7 @@ fn test_init_config_and_init_mint() { vec![init_mint_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor-v1/programs/abl-token/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml index ba44d62c8..a9dce9e2b 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/Cargo.toml @@ -28,12 +28,10 @@ spl-transfer-hook-interface = "0.9.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs index 820227e2e..90defca81 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/test_transfer_hook_counter.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -11,7 +7,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -71,6 +67,7 @@ fn test_transfer_hook_counter() { &[MintExtension::TransferHook { program_id: program_id, }], + None, ) .unwrap(); svm.expire_blockhash(); @@ -82,14 +79,16 @@ fn test_transfer_hook_counter() { let amount: u64 = 100 * 10u64.pow(decimals as u32); let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer, None).unwrap(); svm.expire_blockhash(); let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) + .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) + .unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates counter PDA) @@ -107,8 +106,14 @@ fn test_transfer_hook_counter() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with hook (this triggers the counter increment) @@ -131,6 +136,7 @@ fn test_transfer_hook_counter() { transfer_amount, decimals, &extra_accounts, + None, ) .unwrap(); svm.expire_blockhash(); @@ -158,6 +164,7 @@ fn test_transfer_hook_counter() { vec![direct_hook_ix], &[&payer], &payer.pubkey(), + None, ); assert!( result.is_err(), diff --git a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/counter/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml index ba44d62c8..a9dce9e2b 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/Cargo.toml @@ -28,12 +28,10 @@ spl-transfer-hook-interface = "0.9.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 2df6e8174..f80ff0d79 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, get_token_extensions_account_address, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -68,15 +64,21 @@ fn test_transfer_hook_hello_world() { vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) - .unwrap(); + let source_ata = create_token_extensions_account( + &mut svm, + &payer.pubkey(), + &mint_keypair.pubkey(), + &payer, + None, + ) + .unwrap(); svm.expire_blockhash(); let dest_ata = create_token_extensions_account( @@ -84,6 +86,7 @@ fn test_transfer_hook_hello_world() { &recipient.pubkey(), &mint_keypair.pubkey(), &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -94,6 +97,7 @@ fn test_transfer_hook_hello_world() { &source_ata, amount, &payer, + None, ) .unwrap(); svm.expire_blockhash(); @@ -112,8 +116,14 @@ fn test_transfer_hook_hello_world() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with transfer hook @@ -132,6 +142,7 @@ fn test_transfer_hook_hello_world() { transfer_amount, decimals, &extra_accounts, + None, ) .unwrap(); svm.expire_blockhash(); @@ -154,6 +165,7 @@ fn test_transfer_hook_hello_world() { vec![direct_hook_ix], &[&payer], &payer.pubkey(), + None, ); assert!( result.is_err(), diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml index aa607c4c8..1611f0ce8 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/Cargo.toml @@ -33,12 +33,10 @@ spl-transfer-hook-interface = "2.1.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index 0c5614063..a7a38343a 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_mint, MintExtension, TOKEN_EXTENSIONS_PROGRAM_ID, }, @@ -44,6 +40,7 @@ fn test_initialize_extra_account_meta_list() { &[MintExtension::TransferHook { program_id: program_id, }], + None, ) .unwrap(); svm.expire_blockhash(); @@ -65,8 +62,14 @@ fn test_initialize_extra_account_meta_list() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify the ExtraAccountMetaList account was created let account = svm.get_account(&extra_account_meta_list); diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml index d95456846..0bbb52a90 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/Cargo.toml @@ -28,12 +28,10 @@ spl-transfer-hook-interface = "0.9.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs index b9f89c2c5..d741d73af 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/test_transfer_switch.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -52,6 +48,7 @@ fn test_transfer_switch() { &[MintExtension::TransferHook { program_id: program_id, }], + None, ) .unwrap(); svm.expire_blockhash(); @@ -61,14 +58,16 @@ fn test_transfer_switch() { // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); let source_ata = - create_token_extensions_account(&mut svm, &sender.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &sender.pubkey(), &mint, &payer, None).unwrap(); svm.expire_blockhash(); let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) + .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) + .unwrap(); svm.expire_blockhash(); // Step 3: Configure admin @@ -88,6 +87,7 @@ fn test_transfer_switch() { vec![configure_admin_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -104,8 +104,14 @@ fn test_transfer_switch() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Turn transfers OFF for sender @@ -121,8 +127,14 @@ fn test_transfer_switch() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![switch_off_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![switch_off_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 6: Try transfer - should FAIL (switch is off) @@ -145,6 +157,7 @@ fn test_transfer_switch() { transfer_amount, decimals, &extra_accounts, + None, ); assert!(result.is_err(), "Transfer should fail when switch is off"); svm.expire_blockhash(); @@ -162,8 +175,14 @@ fn test_transfer_switch() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![switch_on_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![switch_on_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 8: Transfer - should SUCCEED (switch is on) @@ -176,6 +195,7 @@ fn test_transfer_switch() { transfer_amount, decimals, &extra_accounts, + None, ) .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1/programs/transfer-switch/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml index ec2529de7..e2d51e19a 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml @@ -28,12 +28,10 @@ spl-transfer-hook-interface = "0.9.0" [dev-dependencies] litesvm = "0.16.0" -solana-kite = "0.5.0" +solana-kite = "0.6.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs index c59fd8c40..66017e72c 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -10,7 +6,7 @@ use { litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ - create_wallet, + create_wallet, send_transaction_from_instructions, token_extensions::{ create_token_extensions_account, create_token_extensions_mint, mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, @@ -50,6 +46,7 @@ fn test_whitelist_transfer_hook() { &[MintExtension::TransferHook { program_id: program_id, }], + None, ) .unwrap(); svm.expire_blockhash(); @@ -59,14 +56,16 @@ fn test_whitelist_transfer_hook() { // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer, None).unwrap(); svm.expire_blockhash(); let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) + .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) + .unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates whitelist) @@ -82,8 +81,14 @@ fn test_whitelist_transfer_hook() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_extra_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Add destination token account to whitelist @@ -102,6 +107,7 @@ fn test_whitelist_transfer_hook() { vec![add_to_whitelist_ix], &[&payer], &payer.pubkey(), + None, ) .unwrap(); svm.expire_blockhash(); @@ -126,6 +132,7 @@ fn test_whitelist_transfer_hook() { transfer_amount, decimals, &extra_accounts, + None, ) .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml b/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml index 6e1371a12..1f6f82252 100644 --- a/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml +++ b/tokens/token-minter/anchor-v1/programs/token-minter/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs b/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs index b393727bb..7a5feedb9 100644 --- a/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs +++ b/tokens/token-minter/anchor-v1/programs/token-minter/tests/test_token_minter.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance}, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -109,6 +105,7 @@ fn test_create_token() { vec![create_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -156,6 +153,7 @@ fn test_create_and_mint_tokens() { vec![create_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -180,7 +178,7 @@ fn test_create_and_mint_tokens() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify 100 tokens minted (in minor units) diff --git a/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs b/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/token-minter/anchor-v1/programs/token-minter/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml index 7b1a58886..702a56438 100644 --- a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml +++ b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/Cargo.toml @@ -27,10 +27,8 @@ anchor-spl = { version = "1.2.0", features = ["metadata"] } litesvm = "0.16.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" -solana-kite = "0.5.0" +solana-kite = "0.6.0" borsh = "1.6.1" -solana-message = "4.2.4" -solana-transaction = "4.1.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs index d2769ba36..0bc8ab33d 100644 --- a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs +++ b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/test_transfer_tokens.rs @@ -1,7 +1,3 @@ -mod transaction_v1; - -use transaction_v1::send_transaction_from_instructions; - use { anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, @@ -9,7 +5,7 @@ use { }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{create_wallet, get_token_account_balance}, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -110,6 +106,7 @@ fn test_create_mint_and_transfer() { vec![create_ix], &[&payer, &mint_keypair], &payer.pubkey(), + None, ) .unwrap(); @@ -140,7 +137,7 @@ fn test_create_mint_and_transfer() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey(), None) .unwrap(); // Verify 100 tokens minted (in minor units) @@ -172,8 +169,14 @@ fn test_create_mint_and_transfer() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) - .unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![transfer_ix], + &[&payer], + &payer.pubkey(), + None, + ) + .unwrap(); // Verify: sender 50 tokens, recipient 50 tokens (in minor units) assert_eq!( diff --git a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs b/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs deleted file mode 100644 index 3450a5eb7..000000000 --- a/tokens/transfer-tokens/anchor-v1/programs/transfer-tokens/tests/transaction_v1/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Send this example's test transactions in Solana's v1 transaction format -//! (SIMD-0385), live on mainnet since epoch 1035. -//! -//! `send_transaction_from_instructions` has the same name, arguments and error -//! type as `solana_kite::send_transaction_from_instructions`, so a test swaps -//! one import and nothing else. Kite's version builds a legacy transaction. -//! -//! A v1 transaction carries its resource limits in a `TransactionConfig` in the -//! message instead of in ComputeBudget instructions, and a limit it leaves -//! unset is zero rather than the legacy default. Every transaction built here -//! asks for what a legacy transaction gets without ComputeBudget instructions, -//! so the programs under test see the same budget they always did. - -// Each test crate in this directory compiles its own copy of this module and -// uses only what it needs from it. -#![allow(dead_code)] - -use { - anchor_lang::solana_program::{instruction::Instruction, pubkey::Pubkey}, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::SolanaKiteError, - solana_message::{v1, VersionedMessage}, - solana_signer::Signer, - solana_transaction::versioned::VersionedTransaction, -}; - -/// The default compute unit limit of a legacy transaction is 200,000 per -/// instruction. -const COMPUTE_UNITS_PER_INSTRUCTION: u32 = 200_000; - -/// The most any transaction may ask for, and the cap on the legacy default. -const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// 64 MiB: the most account data any transaction may load, and the legacy -/// default. -const MAX_LOADED_ACCOUNTS_DATA_SIZE: u32 = 64 * 1024 * 1024; - -/// The config a legacy transaction with these instructions would get by -/// default. -pub fn transaction_config(instruction_count: usize) -> v1::TransactionConfig { - let compute_unit_limit = u32::try_from(instruction_count) - .unwrap_or(u32::MAX) - .saturating_mul(COMPUTE_UNITS_PER_INSTRUCTION) - .min(MAX_COMPUTE_UNIT_LIMIT); - v1::TransactionConfig::empty() - .with_compute_unit_limit(compute_unit_limit) - .with_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE) -} - -/// Compile and sign a v1 transaction. Panics if the instructions do not fit -/// a v1 message or the signers do not match it, as kite's legacy builder does. -pub fn v1_transaction( - svm: &LiteSVM, - instructions: &[Instruction], - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> VersionedTransaction { - let message = v1::Message::try_compile_with_config( - fee_payer, - instructions, - svm.latest_blockhash(), - transaction_config(instructions.len()), - ) - .expect("instructions do not fit in a v1 message"); - - // A legacy transaction accepts the same keypair twice (a fee payer that is - // also a named signer, say); a v1 one wants each signer once. - let mut unique_signers: Vec<&Keypair> = Vec::with_capacity(signers.len()); - for signer in signers { - if !unique_signers - .iter() - .any(|seen| seen.pubkey() == signer.pubkey()) - { - unique_signers.push(signer); - } - } - - VersionedTransaction::try_new(VersionedMessage::V1(message), &unique_signers) - .expect("signers do not match the transaction") -} - -/// `solana_kite::send_transaction_from_instructions`, sending a v1 transaction. -pub fn send_transaction_from_instructions( - svm: &mut LiteSVM, - instructions: Vec, - signers: &[&Keypair], - fee_payer: &Pubkey, -) -> Result<(), SolanaKiteError> { - let transaction = v1_transaction(svm, &instructions, signers, fee_payer); - svm.send_transaction(transaction) - .map(|_| ()) - .map_err(|e| SolanaKiteError::TransactionFailed(format!("{e:?}"))) -} From ced1cdad854020a42665cf409395bd0e9f083bfa Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 15 Oct 2026 04:04:49 +0000 Subject: [PATCH 4/4] Build kite's SPL dependencies without entrypoints in the compression tests anchor build failed for compression/cnft-vault: the IDL build links the test binary, and both cnft_vault and spl-token 9 defined `entrypoint`. spl-token came in through solana-kite 0.6.0, which depends on spl-token 9 and spl-associated-token-account 8 without their `no-entrypoint` feature. The three compression test manifests now name both crates with `no-entrypoint`, which Cargo's feature unification applies to kite's copies too. A TODO says to delete them once kite turns the feature on itself. Claude-Session: https://claude.ai/code/session_014EotgxjmAGtATDFVnBtjXj --- CHANGELOG.md | 5 ++++- .../cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml | 7 +++++++ .../cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml | 7 +++++++ compression/cutils/anchor-v1/programs/cutils/Cargo.toml | 7 +++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce166974a..d745c4404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,10 @@ Programs need no change for it; the client that builds the transaction does. units or read a program error code) build it with kite's `build_transaction_from_instructions` and `get_transaction_config`, with `MAX_COMPUTE_UNIT_LIMIT` from `solana-compute-budget`, and send it through LiteSVM. - The three compression examples add kite for this. They, `hello-solana`, `options` + The three compression examples add kite for this, and turn on `no-entrypoint` for + kite's `spl-token` and `spl-associated-token-account`, whose program entrypoints + otherwise clash with the example program's when the test binary links. They, + `hello-solana`, `options` and `order-book` no longer use `solana-message` or `solana-transaction` directly, and drop them from their manifests. diff --git a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml index 8ad72463d..4975f2da6 100644 --- a/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor-v1/programs/cnft-burn/Cargo.toml @@ -37,6 +37,13 @@ solana-signer = "3.0.0" # Builds each v1 transaction the tests send. They send it through LiteSVM # themselves to read the program error a failure carries. solana-kite = "0.6.0" +# TODO: delete these two once solana-kite turns on `no-entrypoint` for them +# itself. kite 0.6.0 builds both with their program entrypoints, which clash +# with this program's `entrypoint` symbol when the test binary links (anchor's +# IDL build failed with `duplicate symbol: entrypoint`). Naming the same +# versions here turns the feature on through Cargo's feature unification. +spl-token = { version = "9.0.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] } # The named limit those transactions get. Same version solana-kite 0.6 builds # against. solana-compute-budget = "4.2" diff --git a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml index 18183ff9a..eee1f47c9 100644 --- a/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor-v1/programs/cnft-vault/Cargo.toml @@ -40,6 +40,13 @@ solana-signer = "3.0.0" # Builds each v1 transaction the tests send. They send it through LiteSVM # themselves to read the program error a failure carries. solana-kite = "0.6.0" +# TODO: delete these two once solana-kite turns on `no-entrypoint` for them +# itself. kite 0.6.0 builds both with their program entrypoints, which clash +# with this program's `entrypoint` symbol when the test binary links (anchor's +# IDL build failed with `duplicate symbol: entrypoint`). Naming the same +# versions here turns the feature on through Cargo's feature unification. +spl-token = { version = "9.0.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] } # The named limit those transactions get. Same version solana-kite 0.6 builds # against. solana-compute-budget = "4.2" diff --git a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml index 092cae680..f397fd74d 100644 --- a/compression/cutils/anchor-v1/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor-v1/programs/cutils/Cargo.toml @@ -42,6 +42,13 @@ solana-signer = "3.0.0" # Builds each v1 transaction the tests send. They send it through LiteSVM # themselves to read the program error a failure carries. solana-kite = "0.6.0" +# TODO: delete these two once solana-kite turns on `no-entrypoint` for them +# itself. kite 0.6.0 builds both with their program entrypoints, which clash +# with this program's `entrypoint` symbol when the test binary links (anchor's +# IDL build failed with `duplicate symbol: entrypoint`). Naming the same +# versions here turns the feature on through Cargo's feature unification. +spl-token = { version = "9.0.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] } # The named limit those transactions get. Same version solana-kite 0.6 builds # against. solana-compute-budget = "4.2"