From 3927141ccc335efde9e75f68fea111f2fe7e8a1e Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 5 Feb 2025 23:55:27 +0000 Subject: [PATCH 01/12] Update workspace --- Cargo.lock | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f30c2e0f..6de43135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2680,6 +2680,7 @@ checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -5460,6 +5461,173 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-rpc-client" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b40d68b77b47a7786965eca51207dd19cb68bb518da7476e84cc4f87f5c334" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bincode", + "bs58", + "indicatif", + "log", + "reqwest", + "reqwest-middleware", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-rpc-client-api", + "solana-sdk", + "solana-transaction-status-client-types", + "solana-version", + "solana-vote-program", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4520467a0bb012c7ecf121eaae0182d4c3c0647844c6bbcbeea87997a9cdc97e" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bs58", + "jsonrpc-core", + "reqwest", + "reqwest-middleware", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-inline-spl", + "solana-sdk", + "solana-transaction-status-client-types", + "solana-version", + "thiserror 1.0.69", +] + +[[package]] +name = "solana-rpc-client-nonce-utils" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1396307c7e3a72ed8074cb1c31f7f6613d3e71f0f3414911ccbaeea29690158d" +dependencies = [ + "solana-rpc-client", + "solana-sdk", + "thiserror 1.0.69", +] + +[[package]] +name = "solana-runtime" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cebb07bfc23c41c5f63dd00f62a377e9d30903e551ddc570aced74ab729c340b" +dependencies = [ + "ahash", + "aquamarine", + "arrayref", + "base64 0.22.1", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "im", + "index_list", + "itertools 0.12.1", + "lazy_static", + "libc", + "log", + "lz4", + "memmap2", + "mockall", + "modular-bitfield", + "num-derive", + "num-traits", + "num_cpus", + "num_enum", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "serde_with", + "solana-accounts-db", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-bucket-map", + "solana-compute-budget", + "solana-compute-budget-program", + "solana-config-program", + "solana-cost-model", + "solana-feature-set", + "solana-fee", + "solana-inline-spl", + "solana-lattice-hash", + "solana-loader-v4-program", + "solana-measure", + "solana-metrics", + "solana-perf", + "solana-program", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-runtime-transaction", + "solana-sdk", + "solana-stake-program", + "solana-svm", + "solana-svm-rent-collector", + "solana-svm-transaction", + "solana-system-program", + "solana-timings", + "solana-transaction-status", + "solana-version", + "solana-vote", + "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-sdk", + "solana-zk-token-proof-program", + "solana-zk-token-sdk", + "static_assertions", + "strum", + "strum_macros", + "symlink", + "tar", + "tempfile", + "thiserror 1.0.69", + "zstd", +] + +[[package]] +name = "solana-runtime-transaction" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242b34f49b0c31f8f10681e4f0c15f4e5d49da7da89ce7f524e0877d5ce8e9cc" +dependencies = [ + "agave-transaction-view", + "log", + "solana-builtins-default-costs", + "solana-compute-budget", + "solana-pubkey", + "solana-sdk", + "solana-svm-transaction", + "thiserror 1.0.69", +] + [[package]] name = "solana-rent-collector" version = "2.2.1" @@ -6022,6 +6190,109 @@ dependencies = [ "solana-pubkey", ] +[[package]] +name = "solana-stake-program" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803aae3aa3c4b344bd6eb2107dd7a897f15b9614af4d5c6dbaf9026bc39a1fe" +dependencies = [ + "bincode", + "log", + "solana-config-program", + "solana-feature-set", + "solana-log-collector", + "solana-program-runtime", + "solana-sdk", + "solana-type-overrides", + "solana-vote-program", +] + +[[package]] +name = "solana-streamer" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85090db4563b271711d44275a20d1becb4a92e2fdeb41f5234b45df0321e807d" +dependencies = [ + "async-channel", + "bytes", + "crossbeam-channel", + "dashmap", + "futures", + "futures-util", + "governor", + "histogram", + "indexmap", + "itertools 0.12.1", + "libc", + "log", + "nix", + "pem", + "percentage", + "quinn", + "quinn-proto", + "rand 0.8.5", + "rustls 0.23.22", + "smallvec", + "socket2", + "solana-measure", + "solana-metrics", + "solana-perf", + "solana-sdk", + "solana-transaction-metrics-tracker", + "thiserror 1.0.69", + "tokio", + "tokio-util 0.7.13", + "x509-parser", +] + +[[package]] +name = "solana-svm" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee008a28c6c24be2ae5d0dad2df607201af975ec3cb4ea4f26d0dd1c6d05aa7a" +dependencies = [ + "itertools 0.12.1", + "log", + "percentage", + "serde", + "serde_derive", + "solana-bpf-loader-program", + "solana-compute-budget", + "solana-feature-set", + "solana-fee", + "solana-loader-v4-program", + "solana-log-collector", + "solana-measure", + "solana-program-runtime", + "solana-runtime-transaction", + "solana-sdk", + "solana-svm-rent-collector", + "solana-svm-transaction", + "solana-system-program", + "solana-timings", + "solana-type-overrides", + "solana-vote", + "thiserror 1.0.69", +] + +[[package]] +name = "solana-svm-rent-collector" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c1360c7382ec61503aab743068c443fd235e1087adb71216de6f03612062a5" +dependencies = [ + "solana-sdk", +] + +[[package]] +name = "solana-svm-transaction" +version = "2.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cce3a10755daa103d6c5bcb119515e8a44db6c69d2e37b54704128e11c5356" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-stake-interface" version = "1.2.1" @@ -7160,6 +7431,28 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-interface" +version = "0.0.0" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", +] + +[[package]] +name = "token-program" +version = "0.0.0" +dependencies = [ + "assert_matches", + "pinocchio", + "pinocchio-log", + "solana-program-test", + "solana-sdk", + "spl-token 4.0.2", + "test-case", + "token-interface", +] + [[package]] name = "tokio" version = "1.43.0" From de612187614d3e99387d9b0e6e6ec09f5b3ddb8a Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 13 Feb 2025 23:21:23 +0000 Subject: [PATCH 02/12] Rename interface crate --- Cargo.lock | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6de43135..dc1457fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7431,28 +7431,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "token-interface" -version = "0.0.0" -dependencies = [ - "pinocchio", - "pinocchio-pubkey", -] - -[[package]] -name = "token-program" -version = "0.0.0" -dependencies = [ - "assert_matches", - "pinocchio", - "pinocchio-log", - "solana-program-test", - "solana-sdk", - "spl-token 4.0.2", - "test-case", - "token-interface", -] - [[package]] name = "tokio" version = "1.43.0" From f038204837fdc870d051812f0b7037cd8d72b8cd Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 6 Feb 2025 00:46:38 +0000 Subject: [PATCH 03/12] Fix spelling --- Cargo.lock | 271 ----------------------------------------------------- 1 file changed, 271 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc1457fd..f30c2e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2680,7 +2680,6 @@ checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -5461,173 +5460,6 @@ dependencies = [ "solana-sysvar-id", ] -[[package]] -name = "solana-rpc-client" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b40d68b77b47a7786965eca51207dd19cb68bb518da7476e84cc4f87f5c334" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bincode", - "bs58", - "indicatif", - "log", - "reqwest", - "reqwest-middleware", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder-client-types", - "solana-rpc-client-api", - "solana-sdk", - "solana-transaction-status-client-types", - "solana-version", - "solana-vote-program", - "tokio", -] - -[[package]] -name = "solana-rpc-client-api" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4520467a0bb012c7ecf121eaae0182d4c3c0647844c6bbcbeea87997a9cdc97e" -dependencies = [ - "anyhow", - "base64 0.22.1", - "bs58", - "jsonrpc-core", - "reqwest", - "reqwest-middleware", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder-client-types", - "solana-inline-spl", - "solana-sdk", - "solana-transaction-status-client-types", - "solana-version", - "thiserror 1.0.69", -] - -[[package]] -name = "solana-rpc-client-nonce-utils" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1396307c7e3a72ed8074cb1c31f7f6613d3e71f0f3414911ccbaeea29690158d" -dependencies = [ - "solana-rpc-client", - "solana-sdk", - "thiserror 1.0.69", -] - -[[package]] -name = "solana-runtime" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cebb07bfc23c41c5f63dd00f62a377e9d30903e551ddc570aced74ab729c340b" -dependencies = [ - "ahash", - "aquamarine", - "arrayref", - "base64 0.22.1", - "bincode", - "blake3", - "bv", - "bytemuck", - "byteorder", - "bzip2", - "crossbeam-channel", - "dashmap", - "dir-diff", - "flate2", - "fnv", - "im", - "index_list", - "itertools 0.12.1", - "lazy_static", - "libc", - "log", - "lz4", - "memmap2", - "mockall", - "modular-bitfield", - "num-derive", - "num-traits", - "num_cpus", - "num_enum", - "percentage", - "qualifier_attr", - "rand 0.8.5", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "serde_with", - "solana-accounts-db", - "solana-address-lookup-table-program", - "solana-bpf-loader-program", - "solana-bucket-map", - "solana-compute-budget", - "solana-compute-budget-program", - "solana-config-program", - "solana-cost-model", - "solana-feature-set", - "solana-fee", - "solana-inline-spl", - "solana-lattice-hash", - "solana-loader-v4-program", - "solana-measure", - "solana-metrics", - "solana-perf", - "solana-program", - "solana-program-runtime", - "solana-rayon-threadlimit", - "solana-runtime-transaction", - "solana-sdk", - "solana-stake-program", - "solana-svm", - "solana-svm-rent-collector", - "solana-svm-transaction", - "solana-system-program", - "solana-timings", - "solana-transaction-status", - "solana-version", - "solana-vote", - "solana-vote-program", - "solana-zk-elgamal-proof-program", - "solana-zk-sdk", - "solana-zk-token-proof-program", - "solana-zk-token-sdk", - "static_assertions", - "strum", - "strum_macros", - "symlink", - "tar", - "tempfile", - "thiserror 1.0.69", - "zstd", -] - -[[package]] -name = "solana-runtime-transaction" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242b34f49b0c31f8f10681e4f0c15f4e5d49da7da89ce7f524e0877d5ce8e9cc" -dependencies = [ - "agave-transaction-view", - "log", - "solana-builtins-default-costs", - "solana-compute-budget", - "solana-pubkey", - "solana-sdk", - "solana-svm-transaction", - "thiserror 1.0.69", -] - [[package]] name = "solana-rent-collector" version = "2.2.1" @@ -6190,109 +6022,6 @@ dependencies = [ "solana-pubkey", ] -[[package]] -name = "solana-stake-program" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803aae3aa3c4b344bd6eb2107dd7a897f15b9614af4d5c6dbaf9026bc39a1fe" -dependencies = [ - "bincode", - "log", - "solana-config-program", - "solana-feature-set", - "solana-log-collector", - "solana-program-runtime", - "solana-sdk", - "solana-type-overrides", - "solana-vote-program", -] - -[[package]] -name = "solana-streamer" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85090db4563b271711d44275a20d1becb4a92e2fdeb41f5234b45df0321e807d" -dependencies = [ - "async-channel", - "bytes", - "crossbeam-channel", - "dashmap", - "futures", - "futures-util", - "governor", - "histogram", - "indexmap", - "itertools 0.12.1", - "libc", - "log", - "nix", - "pem", - "percentage", - "quinn", - "quinn-proto", - "rand 0.8.5", - "rustls 0.23.22", - "smallvec", - "socket2", - "solana-measure", - "solana-metrics", - "solana-perf", - "solana-sdk", - "solana-transaction-metrics-tracker", - "thiserror 1.0.69", - "tokio", - "tokio-util 0.7.13", - "x509-parser", -] - -[[package]] -name = "solana-svm" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee008a28c6c24be2ae5d0dad2df607201af975ec3cb4ea4f26d0dd1c6d05aa7a" -dependencies = [ - "itertools 0.12.1", - "log", - "percentage", - "serde", - "serde_derive", - "solana-bpf-loader-program", - "solana-compute-budget", - "solana-feature-set", - "solana-fee", - "solana-loader-v4-program", - "solana-log-collector", - "solana-measure", - "solana-program-runtime", - "solana-runtime-transaction", - "solana-sdk", - "solana-svm-rent-collector", - "solana-svm-transaction", - "solana-system-program", - "solana-timings", - "solana-type-overrides", - "solana-vote", - "thiserror 1.0.69", -] - -[[package]] -name = "solana-svm-rent-collector" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c1360c7382ec61503aab743068c443fd235e1087adb71216de6f03612062a5" -dependencies = [ - "solana-sdk", -] - -[[package]] -name = "solana-svm-transaction" -version = "2.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cce3a10755daa103d6c5bcb119515e8a44db6c69d2e37b54704128e11c5356" -dependencies = [ - "solana-sdk", -] - [[package]] name = "solana-stake-interface" version = "1.2.1" From c91bfaf129a8783eb469fd765696226d9da3c908 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 5 Mar 2025 14:14:58 +0000 Subject: [PATCH 04/12] [wip]: Fix review comments --- p-token/src/processor/set_authority.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index b621eeba..e4b8f43a 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -45,11 +45,11 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) return Err(TokenError::AccountFrozen.into()); } - match authority_type { + match args.authority_type { AuthorityType::AccountOwner => { validate_owner(&account.owner, authority_info, remaining)?; - if let Some(authority) = new_authority { + if let Some(authority) = args.new_authority { account.owner = *authority; } else { return Err(TokenError::InvalidInstruction.into()); @@ -66,7 +66,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) let authority = account.close_authority().unwrap_or(&account.owner); validate_owner(authority, authority_info, remaining)?; - if let Some(authority) = new_authority { + if let Some(authority) = args.new_authority { account.set_close_authority(authority); } else { account.clear_close_authority(); @@ -81,7 +81,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; - match authority_type { + match args.authority_type { AuthorityType::MintTokens => { // Once a mint's supply is fixed, it cannot be undone by setting a new // mint_authority. @@ -89,7 +89,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(mint_authority, authority_info, remaining)?; - if let Some(authority) = new_authority { + if let Some(authority) = args.new_authority { mint.set_mint_authority(authority); } else { mint.clear_mint_authority(); @@ -104,7 +104,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(freeze_authority, authority_info, remaining)?; - if let Some(authority) = new_authority { + if let Some(authority) = args.new_authority { mint.set_freeze_authority(authority); } else { mint.clear_freeze_authority(); From 700b33dbf9d94032fe9cf2f83c2100601265950d Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 6 Mar 2025 19:31:56 +0000 Subject: [PATCH 05/12] A few more fixes --- p-token/src/processor/set_authority.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index e4b8f43a..c3c8568f 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -45,11 +45,11 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) return Err(TokenError::AccountFrozen.into()); } - match args.authority_type { + match args.authority_type()? { AuthorityType::AccountOwner => { validate_owner(&account.owner, authority_info, remaining)?; - if let Some(authority) = args.new_authority { + if let Some(authority) = args.new_authority() { account.owner = *authority; } else { return Err(TokenError::InvalidInstruction.into()); @@ -66,7 +66,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) let authority = account.close_authority().unwrap_or(&account.owner); validate_owner(authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority { + if let Some(authority) = args.new_authority() { account.set_close_authority(authority); } else { account.clear_close_authority(); @@ -81,7 +81,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; - match args.authority_type { + match args.authority_type()? { AuthorityType::MintTokens => { // Once a mint's supply is fixed, it cannot be undone by setting a new // mint_authority. @@ -89,7 +89,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(mint_authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority { + if let Some(authority) = args.new_authority() { mint.set_mint_authority(authority); } else { mint.clear_mint_authority(); @@ -104,7 +104,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(freeze_authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority { + if let Some(authority) = args.new_authority() { mint.set_freeze_authority(authority); } else { mint.clear_freeze_authority(); From 6841a9eb345a6e3aa9a142a0d52180f4568ae0fa Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 7 Mar 2025 01:21:36 +0000 Subject: [PATCH 06/12] Add batch instruction --- interface/src/instruction.rs | 18 ++++- p-token/src/entrypoint.rs | 140 +++++++++++++++++++-------------- p-token/src/processor/batch.rs | 60 ++++++++++++++ p-token/src/processor/mod.rs | 2 + 4 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 p-token/src/processor/batch.rs diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index e0f747e1..0a529857 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -477,6 +477,21 @@ pub enum TokenInstruction { /// /// - `&str` The `ui_amount` of tokens to reformat. UiAmountToAmount, + + /// Executes a batch of instructions. The instructions to be executed are specified + /// in sequence on the instruction data. Each instruction provides: + /// - `u8`: number of accounts + /// - `u8`: instruction data length (includes the discriminator) + /// - `u8`: instruction discriminator + /// - `[u8]`: instruction data + /// + /// Accounts follow a similar pattern, where accounts for each instruction are + /// specified in sequence. Therefore, the number of accounts expected by this + /// instruction is variable – i.e., it depends on the instructions provided. + /// + /// Both the number of accounts and instruction data length are used to identify + /// the slice of accounts and instruction data for each instruction. + Batch = 255, // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the // latter remains a superset of this instruction set. New variants also need to be added to // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility @@ -485,11 +500,10 @@ pub enum TokenInstruction { impl TryFrom for TokenInstruction { type Error = ProgramError; - #[inline(always)] fn try_from(value: u8) -> Result { match value { // SAFETY: `value` is guaranteed to be in the range of the enum variants. - 0..=24 => Ok(unsafe { core::mem::transmute::(value) }), + 0..=24 | 255 => Ok(unsafe { core::mem::transmute::(value) }), _ => Err(ProgramError::InvalidInstructionData), } } diff --git a/p-token/src/entrypoint.rs b/p-token/src/entrypoint.rs index 80fd04ea..4483f023 100644 --- a/p-token/src/entrypoint.rs +++ b/p-token/src/entrypoint.rs @@ -2,7 +2,6 @@ use pinocchio::{ account_info::AccountInfo, default_panic_handler, no_allocator, program_entrypoint, program_error::ProgramError, pubkey::Pubkey, ProgramResult, }; -use spl_token_interface::instruction::TokenInstruction; use crate::processor::*; @@ -14,218 +13,245 @@ default_panic_handler!(); /// Process an instruction. /// +/// In the first stage, the entrypoint checks the discriminator of the instruction data +/// to determine whether the instruction is a "batch" instruction or a "regular" instruction. +/// This avoids nesting of "batch" instructions, since it is not sound to have a "batch" +/// instruction inside another "batch" instruction. +#[inline(always)] +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let [discriminator, remaining @ ..] = instruction_data else { + return Err(ProgramError::InvalidInstructionData); + }; + + if *discriminator == 255 { + // 255 - Batch + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: Batch"); + + return process_batch(accounts, remaining); + } + + inner_process_instruction(accounts, instruction_data) +} + +/// Process a "regular" instruction. +/// /// The processor of the token program is divided into two parts to reduce the overhead /// of having a large `match` statement. The first part of the processor handles the /// most common instructions, while the second part handles the remaining instructions. +/// /// The rationale is to reduce the overhead of making multiple comparisons for popular /// instructions. /// -/// Instructions on the first part of the processor: +/// Instructions on the first part of the inner processor: /// -/// - `0`: `InitializeMint` -/// - `3`: `Transfer` -/// - `7`: `MintTo` -/// - `9`: `CloseAccount` +/// - `0`: `InitializeMint` +/// - `1`: `InitializeAccount` +/// - `3`: `Transfer` +/// - `7`: `MintTo` +/// - `9`: `CloseAccount` +/// - `16`: `InitializeAccount2` /// - `18`: `InitializeAccount3` /// - `20`: `InitializeMint2` #[inline(always)] -pub fn process_instruction( - _program_id: &Pubkey, +pub(crate) fn inner_process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let (discriminator, instruction_data) = instruction_data - .split_first() - .ok_or(ProgramError::InvalidInstructionData)?; - let instruction = TokenInstruction::try_from(*discriminator)?; + let [discriminator, instruction_data @ ..] = instruction_data else { + return Err(ProgramError::InvalidInstructionData); + }; - match instruction { + match *discriminator { // 0 - InitializeMint - TokenInstruction::InitializeMint => { + 0 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMint"); process_initialize_mint(accounts, instruction_data) } + // 1 - InitializeAccount + 1 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeAccount"); + process_initialize_account(accounts) + } // 3 - Transfer - TokenInstruction::Transfer => { + 3 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Transfer"); process_transfer(accounts, instruction_data) } // 7 - MintTo - TokenInstruction::MintTo => { + 7 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: MintTo"); process_mint_to(accounts, instruction_data) } // 9 - CloseAccount - TokenInstruction::CloseAccount => { + 9 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: CloseAccount"); process_close_account(accounts) } + // 16 - InitializeAccount2 + 16 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeAccount2"); + + process_initialize_account2(accounts, instruction_data) + } // 18 - InitializeAccount3 - TokenInstruction::InitializeAccount3 => { + 18 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeAccount3"); process_initialize_account3(accounts, instruction_data) } // 20 - InitializeMint2 - TokenInstruction::InitializeMint2 => { + 20 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMint2"); process_initialize_mint2(accounts, instruction_data) } - _ => process_remaining_instruction(accounts, instruction_data, instruction), + d => inner_process_remaining_instruction(accounts, instruction_data, d), } } -/// Process the remaining instructions. +/// Process a remaining "regular" instruction. /// -/// This function is called by the `process_instruction` function if the discriminator +/// This function is called by the [`inner_process_instruction`] function if the discriminator /// does not match any of the common instructions. This function is used to reduce the -/// overhead of having a large `match` statement in the `process_instruction` function. -fn process_remaining_instruction( +/// overhead of having a large `match` statement in the [`inner_process_instruction`] function. +fn inner_process_remaining_instruction( accounts: &[AccountInfo], instruction_data: &[u8], - instruction: TokenInstruction, + discriminator: u8, ) -> ProgramResult { - match instruction { - // 1 - InitializeAccount - TokenInstruction::InitializeAccount => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount"); - - process_initialize_account(accounts) - } + match discriminator { // 2 - InitializeMultisig - TokenInstruction::InitializeMultisig => { + 2 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMultisig"); process_initialize_multisig(accounts, instruction_data) } // 4 - Approve - TokenInstruction::Approve => { + 4 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Approve"); process_approve(accounts, instruction_data) } // 5 - Revoke - TokenInstruction::Revoke => { + 5 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Revoke"); process_revoke(accounts, instruction_data) } // 6 - SetAuthority - TokenInstruction::SetAuthority => { + 6 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SetAuthority"); process_set_authority(accounts, instruction_data) } // 8 - Burn - TokenInstruction::Burn => { + 8 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Burn"); process_burn(accounts, instruction_data) } // 10 - FreezeAccount - TokenInstruction::FreezeAccount => { + 10 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: FreezeAccount"); process_freeze_account(accounts) } // 11 - ThawAccount - TokenInstruction::ThawAccount => { + 11 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ThawAccount"); process_thaw_account(accounts) } // 12 - TransferChecked - TokenInstruction::TransferChecked => { + 12 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: TransferChecked"); process_transfer_checked(accounts, instruction_data) } // 13 - ApproveChecked - TokenInstruction::ApproveChecked => { + 13 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ApproveChecked"); process_approve_checked(accounts, instruction_data) } // 14 - MintToChecked - TokenInstruction::MintToChecked => { + 14 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: MintToChecked"); process_mint_to_checked(accounts, instruction_data) } // 15 - BurnChecked - TokenInstruction::BurnChecked => { + 15 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: BurnChecked"); process_burn_checked(accounts, instruction_data) } - // 16 - InitializeAccount2 - TokenInstruction::InitializeAccount2 => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount2"); - - process_initialize_account2(accounts, instruction_data) - } // 17 - SyncNative - TokenInstruction::SyncNative => { + 17 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SyncNative"); process_sync_native(accounts) } // 19 - InitializeMultisig2 - TokenInstruction::InitializeMultisig2 => { + 19 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMultisig2"); process_initialize_multisig2(accounts, instruction_data) } // 21 - GetAccountDataSize - TokenInstruction::GetAccountDataSize => { + 21 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: GetAccountDataSize"); process_get_account_data_size(accounts) } // 22 - InitializeImmutableOwner - TokenInstruction::InitializeImmutableOwner => { + 22 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeImmutableOwner"); process_initialize_immutable_owner(accounts) } // 23 - AmountToUiAmount - TokenInstruction::AmountToUiAmount => { + 23 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: AmountToUiAmount"); process_amount_to_ui_amount(accounts, instruction_data) } // 24 - UiAmountToAmount - TokenInstruction::UiAmountToAmount => { + 24 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: UiAmountToAmount"); diff --git a/p-token/src/processor/batch.rs b/p-token/src/processor/batch.rs new file mode 100644 index 00000000..e1358c49 --- /dev/null +++ b/p-token/src/processor/batch.rs @@ -0,0 +1,60 @@ +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; + +use crate::entrypoint::inner_process_instruction; + +/// The size of the batch instruction header. +/// +/// The header of each instruction consists of two `u8` values: +/// * number of the accounts +/// * length of the instruction data +const IX_HEADER_SIZE: usize = 2; + +pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) -> ProgramResult { + loop { + // Validates the instruction data and accounts offset. + + if instruction_data.len() < IX_HEADER_SIZE { + // The instruction data must have at least two bytes. + return Err(ProgramError::InvalidInstructionData); + } + + // SAFETY: The instruction data is guaranteed to have at least two bytes (header) + // + one byte (discriminator). + let expected_accounts = unsafe { *instruction_data.get_unchecked(0) as usize }; + let data_offset = IX_HEADER_SIZE + unsafe { *instruction_data.get_unchecked(1) as usize }; + + if instruction_data.len() < data_offset || data_offset == 0 { + return Err(ProgramError::InvalidInstructionData); + } + + if accounts.len() < expected_accounts { + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Process the instruction. + + // SAFETY: The instruction data and accounts lengths are already validated so all + // the slices are guaranteed to be valid. + unsafe { + inner_process_instruction( + accounts.get_unchecked(..expected_accounts), + instruction_data.get_unchecked(IX_HEADER_SIZE..data_offset), + //*instruction_data.get_unchecked(IX_HEADER_SIZE), + )?; + } + + if data_offset == instruction_data.len() { + // The batch is complete. + break; + } + + // SAFETY: Both `accounts` and `instruction_data` will have at least the + // expected number of accounts and the data offset, respectively. + //accounts = unsafe { accounts.get_unchecked(expected_accounts..) }; + //instruction_data = unsafe { instruction_data.get_unchecked(data_offset..) }; + accounts = &accounts[expected_accounts..]; + instruction_data = &instruction_data[data_offset..]; + } + + Ok(()) +} diff --git a/p-token/src/processor/mod.rs b/p-token/src/processor/mod.rs index 96e7586d..0002c0f9 100644 --- a/p-token/src/processor/mod.rs +++ b/p-token/src/processor/mod.rs @@ -16,6 +16,7 @@ use spl_token_interface::{ pub mod amount_to_ui_amount; pub mod approve; pub mod approve_checked; +pub mod batch; pub mod burn; pub mod burn_checked; pub mod close_account; @@ -44,6 +45,7 @@ pub mod shared; pub use amount_to_ui_amount::process_amount_to_ui_amount; pub use approve::process_approve; pub use approve_checked::process_approve_checked; +pub use batch::process_batch; pub use burn::process_burn; pub use burn_checked::process_burn_checked; pub use close_account::process_close_account; From 840a2007f56683a936c4a441b5488787947ca39d Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 7 Mar 2025 01:22:36 +0000 Subject: [PATCH 07/12] Add test for batch --- p-token/tests/batch.rs | 223 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 p-token/tests/batch.rs diff --git a/p-token/tests/batch.rs b/p-token/tests/batch.rs new file mode 100644 index 00000000..99932c1d --- /dev/null +++ b/p-token/tests/batch.rs @@ -0,0 +1,223 @@ +#![cfg(feature = "test-sbf")] + +mod setup; + +use crate::setup::TOKEN_PROGRAM_ID; +use solana_program_test::{tokio, ProgramTest}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + signature::{Keypair, Signer}, + system_instruction, + transaction::Transaction, +}; + +fn batch_instruction(instructions: Vec) -> Result { + // Create a `Vec` of ordered `AccountMeta`s + let mut accounts: Vec = vec![]; + // Start with the batch discriminator + let mut data: Vec = vec![0xff]; + + for instruction in instructions { + // Error out on non-token IX. + if instruction.program_id.ne(&spl_token::ID) { + return Err(ProgramError::IncorrectProgramId); + } + + data.push(instruction.accounts.len() as u8); + data.push(instruction.data.len() as u8); + + data.extend_from_slice(&instruction.data); + accounts.extend_from_slice(&instruction.accounts); + } + + Ok(Instruction { + program_id: spl_token::ID, + data, + accounts, + }) +} + +#[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] +#[tokio::test] +async fn batch(token_program: Pubkey) { + let context = ProgramTest::new("pinocchio_token_program", TOKEN_PROGRAM_ID, None) + .start_with_context() + .await; + + let rent = context.banks_client.get_rent().await.unwrap(); + + let mint_len = spl_token::state::Mint::LEN; + let mint_rent = rent.minimum_balance(mint_len); + + let account_len = spl_token::state::Account::LEN; + let account_rent = rent.minimum_balance(account_len); + + // Create a mint + let mint_a = Keypair::new(); + let mint_authority = Keypair::new(); + let create_mint_a = system_instruction::create_account( + &context.payer.pubkey(), + &mint_a.pubkey(), + mint_rent, + mint_len as u64, + &token_program, + ); + let initialize_mint_ix = spl_token::instruction::initialize_mint( + &token_program, + &mint_a.pubkey(), + &mint_authority.pubkey(), + None, + 6, + ) + .unwrap(); + + // Create a mint 2 with a freeze authority + let mint_b = Keypair::new(); + let freeze_authority = Pubkey::new_unique(); + let create_mint_b = system_instruction::create_account( + &context.payer.pubkey(), + &mint_b.pubkey(), + mint_rent, + mint_len as u64, + &token_program, + ); + let initialize_mint_with_freeze_authority_ix = spl_token::instruction::initialize_mint2( + &token_program, + &mint_b.pubkey(), + &mint_authority.pubkey(), + Some(&freeze_authority), + 6, + ) + .unwrap(); + + // Create 2 token accounts for mint A and 1 for mint B + let owner_a = Keypair::new(); + let owner_b = Keypair::new(); + let owner_a_ta_a = Keypair::new(); + let owner_b_ta_a = Keypair::new(); + + let create_owner_a_ta_a = system_instruction::create_account( + &context.payer.pubkey(), + &owner_a_ta_a.pubkey(), + account_rent, + account_len as u64, + &token_program, + ); + let create_owner_b_ta_a = system_instruction::create_account( + &context.payer.pubkey(), + &owner_b_ta_a.pubkey(), + account_rent, + account_len as u64, + &token_program, + ); + let intialize_owner_a_ta_a = spl_token::instruction::initialize_account3( + &token_program, + &owner_a_ta_a.pubkey(), + &mint_a.pubkey(), + &owner_a.pubkey(), + ) + .unwrap(); + let intialize_owner_b_ta_a = spl_token::instruction::initialize_account3( + &token_program, + &owner_b_ta_a.pubkey(), + &mint_a.pubkey(), + &owner_b.pubkey(), + ) + .unwrap(); + + // Mint Token A to Owner A + let mint_token_a_to_owner_a = spl_token::instruction::mint_to( + &token_program, + &mint_a.pubkey(), + &owner_a_ta_a.pubkey(), + &mint_authority.pubkey(), + &[], + 1_000_000, + ) + .unwrap(); + + // Transfer Token A from Owner A to Owner B + let transfer_token_a_to_owner_b = spl_token::instruction::transfer( + &token_program, + &owner_a_ta_a.pubkey(), + &owner_b_ta_a.pubkey(), + &owner_a.pubkey(), + &[], + 1_000_000, + ) + .unwrap(); + + // Close Token A + let close_owner_a_ta_a = spl_token::instruction::close_account( + &token_program, + &owner_a_ta_a.pubkey(), + &owner_a.pubkey(), + &owner_a.pubkey(), + &[], + ) + .unwrap(); + + let batch_ix = batch_instruction(vec![ + initialize_mint_ix, + initialize_mint_with_freeze_authority_ix, + intialize_owner_a_ta_a, + intialize_owner_b_ta_a, + mint_token_a_to_owner_a, + transfer_token_a_to_owner_b, + close_owner_a_ta_a, + ]) + .unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[ + create_mint_a, + create_mint_b, + create_owner_a_ta_a, + create_owner_b_ta_a, + batch_ix, + ], + Some(&context.payer.pubkey()), + &vec![ + &context.payer, + &mint_a, + &mint_b, + &owner_a_ta_a, + &owner_b_ta_a, + &mint_authority, + &owner_a, + ], + context.last_blockhash, + ); + context.banks_client.process_transaction(tx).await.unwrap(); + + let mint_a_account = context + .banks_client + .get_account(mint_a.pubkey()) + .await + .unwrap(); + assert!(mint_a_account.is_some()); + let mint_a_account = spl_token::state::Mint::unpack(&mint_a_account.unwrap().data).unwrap(); + assert_eq!(mint_a_account.supply, 1000000); + + let mint_b_account = context + .banks_client + .get_account(mint_b.pubkey()) + .await + .unwrap(); + assert!(mint_b_account.is_some()); + let mint_b_account = spl_token::state::Mint::unpack(&mint_b_account.unwrap().data).unwrap(); + assert_eq!(mint_b_account.supply, 0); + + let owner_b_ta_a_account = context + .banks_client + .get_account(owner_b_ta_a.pubkey()) + .await + .unwrap(); + assert!(owner_b_ta_a_account.is_some()); + let owner_b_ta_a_account = + spl_token::state::Account::unpack(&owner_b_ta_a_account.unwrap().data).unwrap(); + assert_eq!(owner_b_ta_a_account.amount, 1000000); +} From e76519e24c488c087ad20d38bd38ae8fa149aa38 Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 7 Mar 2025 01:27:56 +0000 Subject: [PATCH 08/12] Clean up comments --- p-token/src/processor/batch.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/p-token/src/processor/batch.rs b/p-token/src/processor/batch.rs index e1358c49..ce78a78f 100644 --- a/p-token/src/processor/batch.rs +++ b/p-token/src/processor/batch.rs @@ -39,7 +39,6 @@ pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) inner_process_instruction( accounts.get_unchecked(..expected_accounts), instruction_data.get_unchecked(IX_HEADER_SIZE..data_offset), - //*instruction_data.get_unchecked(IX_HEADER_SIZE), )?; } @@ -48,10 +47,6 @@ pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) break; } - // SAFETY: Both `accounts` and `instruction_data` will have at least the - // expected number of accounts and the data offset, respectively. - //accounts = unsafe { accounts.get_unchecked(expected_accounts..) }; - //instruction_data = unsafe { instruction_data.get_unchecked(data_offset..) }; accounts = &accounts[expected_accounts..]; instruction_data = &instruction_data[data_offset..]; } From 66b24c3f46d03a38ccce88f94e279109b9333508 Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 7 Mar 2025 02:11:41 +0000 Subject: [PATCH 09/12] Fix spelling --- interface/src/instruction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index 0a529857..fd60ca28 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -487,7 +487,7 @@ pub enum TokenInstruction { /// /// Accounts follow a similar pattern, where accounts for each instruction are /// specified in sequence. Therefore, the number of accounts expected by this - /// instruction is variable – i.e., it depends on the instructions provided. + /// instruction is variable, i.e., it depends on the instructions provided. /// /// Both the number of accounts and instruction data length are used to identify /// the slice of accounts and instruction data for each instruction. From fe2f840ea3fd8c5042026624a11f1f34b0f3eda5 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 10 Mar 2025 16:24:58 +0000 Subject: [PATCH 10/12] Fix merge --- p-token/src/processor/set_authority.rs | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index c3c8568f..b621eeba 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -45,11 +45,11 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) return Err(TokenError::AccountFrozen.into()); } - match args.authority_type()? { + match authority_type { AuthorityType::AccountOwner => { validate_owner(&account.owner, authority_info, remaining)?; - if let Some(authority) = args.new_authority() { + if let Some(authority) = new_authority { account.owner = *authority; } else { return Err(TokenError::InvalidInstruction.into()); @@ -66,7 +66,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) let authority = account.close_authority().unwrap_or(&account.owner); validate_owner(authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority() { + if let Some(authority) = new_authority { account.set_close_authority(authority); } else { account.clear_close_authority(); @@ -81,7 +81,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; - match args.authority_type()? { + match authority_type { AuthorityType::MintTokens => { // Once a mint's supply is fixed, it cannot be undone by setting a new // mint_authority. @@ -89,7 +89,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(mint_authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority() { + if let Some(authority) = new_authority { mint.set_mint_authority(authority); } else { mint.clear_mint_authority(); @@ -104,7 +104,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) validate_owner(freeze_authority, authority_info, remaining)?; - if let Some(authority) = args.new_authority() { + if let Some(authority) = new_authority { mint.set_freeze_authority(authority); } else { mint.clear_freeze_authority(); diff --git a/package.json b/package.json index 77a55265..dfcd1075 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "rust:publish": "zx ./scripts/rust/publish.mjs", "rust:semver": "cargo semver-checks", "p-token:build": "zx ./scripts/rust/build-sbf.mjs p-token", - "p-token:test": "zx ./scripts/rust/test-sbf.mjs p-token" + "p-token:test": "zx ./scripts/rust/test.mjs p-token" }, "devDependencies": { "@codama/renderers-js": "^1.2.7", From 094036b0dcc99f792c12c4a76085d596d49b45d1 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 10 Mar 2025 16:30:14 +0000 Subject: [PATCH 11/12] Remove test-sbf feature --- p-token/Cargo.toml | 1 - p-token/tests/amount_to_ui_amount.rs | 2 -- p-token/tests/approve.rs | 2 -- p-token/tests/approve_checked.rs | 2 -- p-token/tests/batch.rs | 2 -- p-token/tests/burn.rs | 2 -- p-token/tests/burn_checked.rs | 2 -- p-token/tests/close_account.rs | 2 -- p-token/tests/freeze_account.rs | 2 -- p-token/tests/initialize_account.rs | 2 -- p-token/tests/initialize_account2.rs | 2 -- p-token/tests/initialize_account3.rs | 2 -- p-token/tests/initialize_mint.rs | 2 -- p-token/tests/initialize_mint2.rs | 2 -- p-token/tests/initialize_multisig.rs | 2 -- p-token/tests/initialize_multisig2.rs | 2 -- p-token/tests/mint_to.rs | 2 -- p-token/tests/mint_to_checked.rs | 2 -- p-token/tests/revoke.rs | 2 -- p-token/tests/set_authority.rs | 2 -- p-token/tests/thaw_account.rs | 2 -- p-token/tests/transfer.rs | 2 -- p-token/tests/transfer_checked.rs | 2 -- p-token/tests/ui_amount_to_amount.rs | 2 -- 24 files changed, 47 deletions(-) diff --git a/p-token/Cargo.toml b/p-token/Cargo.toml index 63dfb312..d8769984 100644 --- a/p-token/Cargo.toml +++ b/p-token/Cargo.toml @@ -13,7 +13,6 @@ crate-type = ["cdylib"] [features] logging = [] -test-sbf = [] [dependencies] pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } diff --git a/p-token/tests/amount_to_ui_amount.rs b/p-token/tests/amount_to_ui_amount.rs index b9d70799..a8de5314 100644 --- a/p-token/tests/amount_to_ui_amount.rs +++ b/p-token/tests/amount_to_ui_amount.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/approve.rs b/p-token/tests/approve.rs index b47baa71..dbc606a8 100644 --- a/p-token/tests/approve.rs +++ b/p-token/tests/approve.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/approve_checked.rs b/p-token/tests/approve_checked.rs index 43c48378..3fb7c75a 100644 --- a/p-token/tests/approve_checked.rs +++ b/p-token/tests/approve_checked.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/batch.rs b/p-token/tests/batch.rs index 99932c1d..f2cc481e 100644 --- a/p-token/tests/batch.rs +++ b/p-token/tests/batch.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use crate::setup::TOKEN_PROGRAM_ID; diff --git a/p-token/tests/burn.rs b/p-token/tests/burn.rs index df6645d7..67ffb900 100644 --- a/p-token/tests/burn.rs +++ b/p-token/tests/burn.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/burn_checked.rs b/p-token/tests/burn_checked.rs index c2256fd9..a28a8b1f 100644 --- a/p-token/tests/burn_checked.rs +++ b/p-token/tests/burn_checked.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/close_account.rs b/p-token/tests/close_account.rs index d4ece1c5..b9ca15a2 100644 --- a/p-token/tests/close_account.rs +++ b/p-token/tests/close_account.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/freeze_account.rs b/p-token/tests/freeze_account.rs index 2f34b18b..0e8c4983 100644 --- a/p-token/tests/freeze_account.rs +++ b/p-token/tests/freeze_account.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/initialize_account.rs b/p-token/tests/initialize_account.rs index 17a2c11c..0980b97f 100644 --- a/p-token/tests/initialize_account.rs +++ b/p-token/tests/initialize_account.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/initialize_account2.rs b/p-token/tests/initialize_account2.rs index 2ec08051..0e912668 100644 --- a/p-token/tests/initialize_account2.rs +++ b/p-token/tests/initialize_account2.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/initialize_account3.rs b/p-token/tests/initialize_account3.rs index c418fc04..a2349aa2 100644 --- a/p-token/tests/initialize_account3.rs +++ b/p-token/tests/initialize_account3.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/initialize_mint.rs b/p-token/tests/initialize_mint.rs index 94406bd4..4a761a20 100644 --- a/p-token/tests/initialize_mint.rs +++ b/p-token/tests/initialize_mint.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use std::mem::size_of; diff --git a/p-token/tests/initialize_mint2.rs b/p-token/tests/initialize_mint2.rs index 04ce4d4c..7cf32c50 100644 --- a/p-token/tests/initialize_mint2.rs +++ b/p-token/tests/initialize_mint2.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use std::mem::size_of; diff --git a/p-token/tests/initialize_multisig.rs b/p-token/tests/initialize_multisig.rs index 13a518d7..9715545c 100644 --- a/p-token/tests/initialize_multisig.rs +++ b/p-token/tests/initialize_multisig.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::TOKEN_PROGRAM_ID; diff --git a/p-token/tests/initialize_multisig2.rs b/p-token/tests/initialize_multisig2.rs index 40cf0849..d380b3c8 100644 --- a/p-token/tests/initialize_multisig2.rs +++ b/p-token/tests/initialize_multisig2.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::TOKEN_PROGRAM_ID; diff --git a/p-token/tests/mint_to.rs b/p-token/tests/mint_to.rs index 75626cbd..96722ac8 100644 --- a/p-token/tests/mint_to.rs +++ b/p-token/tests/mint_to.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/mint_to_checked.rs b/p-token/tests/mint_to_checked.rs index 56fab40c..f80bd350 100644 --- a/p-token/tests/mint_to_checked.rs +++ b/p-token/tests/mint_to_checked.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/revoke.rs b/p-token/tests/revoke.rs index 9ba9c7f6..2be0ba20 100644 --- a/p-token/tests/revoke.rs +++ b/p-token/tests/revoke.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/set_authority.rs b/p-token/tests/set_authority.rs index 8d3f422b..2b500059 100644 --- a/p-token/tests/set_authority.rs +++ b/p-token/tests/set_authority.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/thaw_account.rs b/p-token/tests/thaw_account.rs index 4d7f9574..b00506ae 100644 --- a/p-token/tests/thaw_account.rs +++ b/p-token/tests/thaw_account.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/transfer.rs b/p-token/tests/transfer.rs index 357ccb6a..e2bd3771 100644 --- a/p-token/tests/transfer.rs +++ b/p-token/tests/transfer.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/transfer_checked.rs b/p-token/tests/transfer_checked.rs index 080bc77e..b599946d 100644 --- a/p-token/tests/transfer_checked.rs +++ b/p-token/tests/transfer_checked.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{account, mint, TOKEN_PROGRAM_ID}; diff --git a/p-token/tests/ui_amount_to_amount.rs b/p-token/tests/ui_amount_to_amount.rs index 37a8788d..3e4754cd 100644 --- a/p-token/tests/ui_amount_to_amount.rs +++ b/p-token/tests/ui_amount_to_amount.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "test-sbf")] - mod setup; use setup::{mint, TOKEN_PROGRAM_ID}; From d69cbd03dab817c1618853bb52b1d536305d72c8 Mon Sep 17 00:00:00 2001 From: febo Date: Tue, 11 Mar 2025 13:46:33 +0000 Subject: [PATCH 12/12] Fix review comments --- interface/src/instruction.rs | 3 +++ p-token/src/processor/batch.rs | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index fd60ca28..6de462c9 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -491,6 +491,9 @@ pub enum TokenInstruction { /// /// Both the number of accounts and instruction data length are used to identify /// the slice of accounts and instruction data for each instruction. + /// + /// Note that it is not sound to have a `batch` instruction that contains other + /// `batch` instruction; an error will be raised when this is detected. Batch = 255, // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the // latter remains a superset of this instruction set. New variants also need to be added to diff --git a/p-token/src/processor/batch.rs b/p-token/src/processor/batch.rs index ce78a78f..6c94208d 100644 --- a/p-token/src/processor/batch.rs +++ b/p-token/src/processor/batch.rs @@ -23,7 +23,7 @@ pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) let expected_accounts = unsafe { *instruction_data.get_unchecked(0) as usize }; let data_offset = IX_HEADER_SIZE + unsafe { *instruction_data.get_unchecked(1) as usize }; - if instruction_data.len() < data_offset || data_offset == 0 { + if instruction_data.len() < data_offset || data_offset == IX_HEADER_SIZE { return Err(ProgramError::InvalidInstructionData); } @@ -34,13 +34,15 @@ pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) // Process the instruction. // SAFETY: The instruction data and accounts lengths are already validated so all - // the slices are guaranteed to be valid. - unsafe { - inner_process_instruction( + // slices are guaranteed to be valid. + let (ix_accounts, ix_data) = unsafe { + ( accounts.get_unchecked(..expected_accounts), instruction_data.get_unchecked(IX_HEADER_SIZE..data_offset), - )?; - } + ) + }; + + inner_process_instruction(ix_accounts, ix_data)?; if data_offset == instruction_data.len() { // The batch is complete.