From 151140dbf2e522aa96bda50c605ed6084afa2087 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 5 Feb 2025 23:55:27 +0000 Subject: [PATCH 1/7] Update workspace --- Cargo.lock | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c75292cd..4ca14cc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2674,6 +2674,7 @@ checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -5454,6 +5455,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" @@ -6016,6 +6184,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" @@ -7133,6 +7404,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 f6f29091131a2ae973fbddbd2057d8b13e766046 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 13 Feb 2025 23:21:23 +0000 Subject: [PATCH 2/7] Rename interface crate --- Cargo.lock | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ca14cc5..56773905 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7404,28 +7404,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 db209d74cfc003b2a36626d4abdb70b75fa678e4 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 6 Feb 2025 00:46:38 +0000 Subject: [PATCH 3/7] Fix spelling --- Cargo.lock | 271 ----------------------------------------------------- 1 file changed, 271 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56773905..c75292cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2674,7 +2674,6 @@ checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -5455,173 +5454,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" @@ -6184,109 +6016,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 c3ae6bdc5cfba5a1560e318bcff94f4fa459a032 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 5 Mar 2025 14:14:58 +0000 Subject: [PATCH 4/7] [wip]: Fix review comments --- interface/src/instruction.rs | 236 ++++++++++-------- interface/src/state/account.rs | 6 +- interface/src/state/mint.rs | 8 +- interface/src/state/mod.rs | 16 +- interface/src/state/multisig.rs | 12 +- p-token/src/entrypoint.rs | 62 ++--- p-token/src/processor/approve_checked.rs | 22 +- p-token/src/processor/burn_checked.rs | 10 +- p-token/src/processor/close_account.rs | 15 +- .../src/processor/get_account_data_size.rs | 2 +- p-token/src/processor/initialize_account2.rs | 17 +- p-token/src/processor/initialize_account3.rs | 17 +- .../processor/initialize_immutable_owner.rs | 4 +- p-token/src/processor/initialize_mint.rs | 122 +-------- p-token/src/processor/initialize_mint2.rs | 2 +- p-token/src/processor/mint_to_checked.rs | 10 +- p-token/src/processor/mod.rs | 25 +- p-token/src/processor/set_authority.rs | 62 ++--- p-token/src/processor/shared/burn.rs | 9 +- .../processor/shared/initialize_account.rs | 16 +- .../src/processor/shared/initialize_mint.rs | 120 +++++++++ .../processor/shared/initialize_multisig.rs | 8 +- p-token/src/processor/shared/mint_to.rs | 11 +- p-token/src/processor/shared/mod.rs | 1 + .../processor/shared/toggle_account_state.rs | 4 +- p-token/src/processor/shared/transfer.rs | 25 +- p-token/src/processor/transfer_checked.rs | 10 +- 27 files changed, 419 insertions(+), 433 deletions(-) create mode 100644 p-token/src/processor/shared/initialize_mint.rs diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index f928cc35..ac8d8f30 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -1,13 +1,13 @@ //! Instruction types. -use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; +use pinocchio::program_error::ProgramError; use crate::error::TokenError; /// Instructions supported by the token program. -#[repr(C)] +#[repr(u8)] #[derive(Clone, Debug, PartialEq)] -pub enum TokenInstruction<'a> { +pub enum TokenInstruction { /// Initializes a new mint and optionally deposits all the newly minted /// tokens in an account. /// @@ -20,15 +20,14 @@ pub enum TokenInstruction<'a> { /// Accounts expected by this instruction: /// /// 0. `[writable]` The mint to initialize. - /// 1. `[]` Rent sysvar - InitializeMint { - /// Number of base 10 digits to the right of the decimal place. - decimals: u8, - /// The authority/multisignature to mint tokens. - mint_authority: Pubkey, - /// The freeze authority/multisignature of the mint. - freeze_authority: Option, - }, + /// 1. `[]` Rent sysvar. + /// + /// Instructions data expected by this instruction: + /// + /// - `u8` The number of base 10 digits to the right of the decimal place. + /// - `Pubkey` The authority/multisignature to mint tokens. + /// - `Option` The freeze authority/multisignature of the mint. + InitializeMint, /// Initializes a new account to hold tokens. If this account is associated /// with the native mint then the token balance of the initialized account @@ -47,7 +46,7 @@ pub enum TokenInstruction<'a> { /// 0. `[writable]` The account to initialize. /// 1. `[]` The mint this account will be associated with. /// 2. `[]` The new account's owner/multisignature. - /// 3. `[]` Rent sysvar + /// 3. `[]` Rent sysvar. InitializeAccount, /// Initializes a multisignature account with N provided signers. @@ -66,13 +65,13 @@ pub enum TokenInstruction<'a> { /// Accounts expected by this instruction: /// /// 0. `[writable]` The multisignature account to initialize. - /// 1. `[]` Rent sysvar + /// 1. `[]` Rent sysvar. /// 2. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`. - InitializeMultisig { - /// The number of signers (M) required to validate this multisignature - /// account. - m: u8, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u8` The number of signers (M) required to validate this multisignature account. + InitializeMultisig, /// Transfers tokens from one account to another either directly or via a /// delegate. If this account is associated with the native mint then equal @@ -91,10 +90,11 @@ pub enum TokenInstruction<'a> { /// 1. `[writable]` The destination account. /// 2. `[]` The source account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. - Transfer { - /// The amount of tokens to transfer. - amount: u64, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens to transfer. + Transfer, /// Approves a delegate. A delegate is given the authority over tokens on /// behalf of the source account's owner. @@ -110,11 +110,12 @@ pub enum TokenInstruction<'a> { /// 0. `[writable]` The source account. /// 1. `[]` The delegate. /// 2. `[]` The source account's multisignature owner. - /// 3. `..+M` `[signer]` M signer accounts - Approve { - /// The amount of tokens the delegate is approved for. - amount: u64, - }, + /// 3. `..+M` `[signer]` M signer accounts. + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens the delegate is approved for. + Approve, /// Revokes the delegate's authority. /// @@ -127,7 +128,7 @@ pub enum TokenInstruction<'a> { /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The source account's multisignature owner. - /// 2. `..+M` `[signer]` M signer accounts + /// 2. `..+M` `[signer]` M signer accounts. Revoke, /// Sets a new authority of a mint or account. @@ -141,13 +142,13 @@ pub enum TokenInstruction<'a> { /// * Multisignature authority /// 0. `[writable]` The mint or account to change the authority of. /// 1. `[]` The mint's or account's current multisignature authority. - /// 2. `..+M` `[signer]` M signer accounts - SetAuthority { - /// The type of authority to update. - authority_type: AuthorityType, - /// The new authority - new_authority: Option, - }, + /// 2. `..+M` `[signer]` M signer accounts. + /// + /// Instructions data expected by this instruction: + /// + /// - `AuthorityType` The type of authority to update. + /// - `Option` The new authority. + SetAuthority, /// Mints new tokens to an account. The native mint does not support /// minting. @@ -164,10 +165,11 @@ pub enum TokenInstruction<'a> { /// 1. `[writable]` The account to mint tokens to. /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. `..+M` `[signer]` M signer accounts. - MintTo { - /// The amount of new tokens to mint. - amount: u64, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of new tokens to mint. + MintTo, /// Burns tokens by removing them from an account. `Burn` does not support /// accounts associated with the native mint, use `CloseAccount` instead. @@ -184,10 +186,11 @@ pub enum TokenInstruction<'a> { /// 1. `[writable]` The token mint. /// 2. `[]` The account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. - Burn { - /// The amount of tokens to burn. - amount: u64, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens to burn. + Burn, /// Close an account by transferring all its SOL to the destination account. /// Non-native accounts may only be closed if its token amount is zero. @@ -262,12 +265,12 @@ pub enum TokenInstruction<'a> { /// 2. `[writable]` The destination account. /// 3. `[]` The source account's multisignature owner/delegate. /// 4. `..+M` `[signer]` M signer accounts. - TransferChecked { - /// The amount of tokens to transfer. - amount: u64, - /// Expected number of base 10 digits to the right of the decimal place. - decimals: u8, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens to transfer. + /// - `u8` Expected number of base 10 digits to the right of the decimal place. + TransferChecked, /// Approves a delegate. A delegate is given the authority over tokens on /// behalf of the source account's owner. @@ -289,13 +292,13 @@ pub enum TokenInstruction<'a> { /// 1. `[]` The token mint. /// 2. `[]` The delegate. /// 3. `[]` The source account's multisignature owner. - /// 4. `..+M` `[signer]` M signer accounts - ApproveChecked { - /// The amount of tokens the delegate is approved for. - amount: u64, - /// Expected number of base 10 digits to the right of the decimal place. - decimals: u8, - }, + /// 4. `..+M` `[signer]` M signer accounts. + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens the delegate is approved for. + /// - `u8` Expected number of base 10 digits to the right of the decimal place. + ApproveChecked, /// Mints new tokens to an account. The native mint does not support /// minting. @@ -316,12 +319,12 @@ pub enum TokenInstruction<'a> { /// 1. `[writable]` The account to mint tokens to. /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. `..+M` `[signer]` M signer accounts. - MintToChecked { - /// The amount of new tokens to mint. - amount: u64, - /// Expected number of base 10 digits to the right of the decimal place. - decimals: u8, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of new tokens to mint. + /// - `u8` Expected number of base 10 digits to the right of the decimal place. + MintToChecked, /// Burns tokens by removing them from an account. [`BurnChecked`] does not /// support accounts associated with the native mint, use `CloseAccount` @@ -343,12 +346,12 @@ pub enum TokenInstruction<'a> { /// 1. `[writable]` The token mint. /// 2. `[]` The account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. - BurnChecked { - /// The amount of tokens to burn. - amount: u64, - /// Expected number of base 10 digits to the right of the decimal place. - decimals: u8, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens to burn. + /// - `u8` Expected number of base 10 digits to the right of the decimal place. + BurnChecked, /// Like [`InitializeAccount`], but the owner pubkey is passed via instruction /// data rather than the accounts list. This variant may be preferable @@ -359,11 +362,12 @@ pub enum TokenInstruction<'a> { /// /// 0. `[writable]` The account to initialize. /// 1. `[]` The mint this account will be associated with. - /// 3. `[]` Rent sysvar - InitializeAccount2 { - /// The new account's owner/multisignature. - owner: Pubkey, - }, + /// 2. `[]` Rent sysvar. + /// + /// Instructions data expected by this instruction: + /// + /// - `Pubkey` The new account's owner/multisignature. + InitializeAccount2, /// Given a wrapped / native token account (a token account containing SOL) /// updates its amount field based on the account's underlying `lamports`. @@ -384,10 +388,11 @@ pub enum TokenInstruction<'a> { /// /// 0. `[writable]` The account to initialize. /// 1. `[]` The mint this account will be associated with. - InitializeAccount3 { - /// The new account's owner/multisignature. - owner: Pubkey, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `Pubkey` The new account's owner/multisignature. + InitializeAccount3, /// Like [`InitializeMultisig`], but does not require the Rent sysvar to be /// provided @@ -396,11 +401,11 @@ pub enum TokenInstruction<'a> { /// /// 0. `[writable]` The multisignature account to initialize. /// 1. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`. - InitializeMultisig2 { - /// The number of signers (M) required to validate this multisignature - /// account. - m: u8, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u8` The number of signers (M) required to validate this multisignature account. + InitializeMultisig2, /// Like [`InitializeMint`], but does not require the Rent sysvar to be /// provided @@ -408,14 +413,13 @@ pub enum TokenInstruction<'a> { /// Accounts expected by this instruction: /// /// 0. `[writable]` The mint to initialize. - InitializeMint2 { - /// Number of base 10 digits to the right of the decimal place. - decimals: u8, - /// The authority/multisignature to mint tokens. - mint_authority: Pubkey, - /// The freeze authority/multisignature of the mint. - freeze_authority: Option, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u8` The number of base 10 digits to the right of the decimal place. + /// - `Pubkey` The authority/multisignature to mint tokens. + /// - `Option` The freeze authority/multisignature of the mint. + InitializeMint2, /// Gets the required size of an account for the given mint as a /// little-endian `u64`. @@ -425,8 +429,8 @@ pub enum TokenInstruction<'a> { /// /// Accounts expected by this instruction: /// - /// 0. `[]` The mint to calculate for - GetAccountDataSize, // typically, there's also data, but this program ignores it + /// 0. `[]` The mint to calculate for. + GetAccountDataSize, /// Initialize the Immutable Owner extension for the given token account /// @@ -439,9 +443,6 @@ pub enum TokenInstruction<'a> { /// Accounts expected by this instruction: /// /// 0. `[writable]` The account to initialize. - /// - /// Data expected by this instruction: - /// None InitializeImmutableOwner, /// Convert an Amount of tokens to a `UiAmount` `string`, using the given @@ -456,10 +457,11 @@ pub enum TokenInstruction<'a> { /// Accounts expected by this instruction: /// /// 0. `[]` The mint to calculate for - AmountToUiAmount { - /// The amount of tokens to reformat. - amount: u64, - }, + /// + /// Instructions data expected by this instruction: + /// + /// - `u64` The amount of tokens to reformat. + AmountToUiAmount, /// Convert a `UiAmount` of tokens to a little-endian `u64` raw Amount, using /// the given mint. In this version of the program, the mint can only @@ -470,16 +472,30 @@ pub enum TokenInstruction<'a> { /// /// Accounts expected by this instruction: /// - /// 0. `[]` The mint to calculate for - UiAmountToAmount { - /// The `ui_amount` of tokens to reformat. - ui_amount: &'a str, - }, + /// 0. `[]` The mint to calculate for. + /// + /// Instructions data expected by this instruction: + /// + /// - `&str` The `ui_amount` of tokens to reformat. + UiAmountToAmount, // 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 } +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) }), + _ => Err(ProgramError::InvalidInstructionData), + } + } +} + /// Specifies the authority type for `SetAuthority` instructions #[repr(u8)] #[derive(Clone, Debug, PartialEq)] @@ -504,12 +520,10 @@ impl AuthorityType { } } - pub fn from(index: u8) -> Result { - match index { - 0 => Ok(AuthorityType::MintTokens), - 1 => Ok(AuthorityType::FreezeAccount), - 2 => Ok(AuthorityType::AccountOwner), - 3 => Ok(AuthorityType::CloseAccount), + #[inline(always)] + pub fn from(value: u8) -> Result { + match value { + 0..=3 => Ok(unsafe { core::mem::transmute::(value) }), _ => Err(TokenError::InvalidInstruction.into()), } } diff --git a/interface/src/state/account.rs b/interface/src/state/account.rs index a4cf6d33..9e5e2c06 100644 --- a/interface/src/state/account.rs +++ b/interface/src/state/account.rs @@ -1,9 +1,9 @@ use pinocchio::pubkey::Pubkey; -use super::{account_state::AccountState, COption, Initializable, RawType}; +use super::{account_state::AccountState, COption, Initializable, Transmutable}; /// Incinerator address. -const INCINERATOR_ID: Pubkey = +pub const INCINERATOR_ID: Pubkey = pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111"); /// System program id. @@ -140,7 +140,7 @@ impl Account { } } -impl RawType for Account { +impl Transmutable for Account { const LEN: usize = core::mem::size_of::(); } diff --git a/interface/src/state/mint.rs b/interface/src/state/mint.rs index 3b4d41b0..46d6168d 100644 --- a/interface/src/state/mint.rs +++ b/interface/src/state/mint.rs @@ -1,6 +1,6 @@ use pinocchio::pubkey::Pubkey; -use super::{COption, Initializable, RawType}; +use super::{COption, Initializable, Transmutable}; /// Internal representation of a mint data. #[repr(C)] @@ -38,8 +38,8 @@ impl Mint { } #[inline(always)] - pub fn set_initialized(&mut self, value: bool) { - self.is_initialized = value as u8; + pub fn set_initialized(&mut self) { + self.is_initialized = 1; } #[inline(always)] @@ -83,7 +83,7 @@ impl Mint { } } -impl RawType for Mint { +impl Transmutable for Mint { /// The length of the `Mint` account data. const LEN: usize = core::mem::size_of::(); } diff --git a/interface/src/state/mod.rs b/interface/src/state/mod.rs index b7c35df6..28ba9f75 100644 --- a/interface/src/state/mod.rs +++ b/interface/src/state/mod.rs @@ -8,11 +8,11 @@ pub mod multisig; /// Type alias for fields represented as `COption`. pub type COption = ([u8; 4], T); -/// Marker trait for types that can cast from a raw pointer. +/// Marker trait for types that can be cast from a raw pointer. /// /// It is up to the type implementing this trait to guarantee that the cast is safe, -/// i.e., that the fields of the type are well aligned and there are no padding bytes. -pub trait RawType { +/// i.e., the fields of the type are well aligned and there are no padding bytes. +pub trait Transmutable { /// The length of the type. /// /// This must be equal to the size of each individual field in the type. @@ -31,7 +31,7 @@ pub trait Initializable { /// /// The caller must ensure that `bytes` contains a valid representation of `T`. #[inline(always)] -pub unsafe fn load(bytes: &[u8]) -> Result<&T, ProgramError> { +pub unsafe fn load(bytes: &[u8]) -> Result<&T, ProgramError> { load_unchecked(bytes).and_then(|t: &T| { // checks if the data is initialized if t.is_initialized() { @@ -50,7 +50,7 @@ pub unsafe fn load(bytes: &[u8]) -> Result<&T, Progr /// /// The caller must ensure that `bytes` contains a valid representation of `T`. #[inline(always)] -pub unsafe fn load_unchecked(bytes: &[u8]) -> Result<&T, ProgramError> { +pub unsafe fn load_unchecked(bytes: &[u8]) -> Result<&T, ProgramError> { if bytes.len() != T::LEN { return Err(ProgramError::InvalidAccountData); } @@ -63,7 +63,7 @@ pub unsafe fn load_unchecked(bytes: &[u8]) -> Result<&T, ProgramErro /// /// The caller must ensure that `bytes` contains a valid representation of `T`. #[inline(always)] -pub unsafe fn load_mut( +pub unsafe fn load_mut( bytes: &mut [u8], ) -> Result<&mut T, ProgramError> { load_mut_unchecked(bytes).and_then(|t: &mut T| { @@ -84,7 +84,9 @@ pub unsafe fn load_mut( /// /// The caller must ensure that `bytes` contains a valid representation of `T`. #[inline(always)] -pub unsafe fn load_mut_unchecked(bytes: &mut [u8]) -> Result<&mut T, ProgramError> { +pub unsafe fn load_mut_unchecked( + bytes: &mut [u8], +) -> Result<&mut T, ProgramError> { if bytes.len() != T::LEN { return Err(ProgramError::InvalidAccountData); } diff --git a/interface/src/state/multisig.rs b/interface/src/state/multisig.rs index 920da97a..d0ad7a34 100644 --- a/interface/src/state/multisig.rs +++ b/interface/src/state/multisig.rs @@ -1,12 +1,12 @@ use pinocchio::pubkey::Pubkey; -use super::{Initializable, RawType}; +use super::{Initializable, Transmutable}; /// Minimum number of multisignature signers (min N) -pub const MIN_SIGNERS: usize = 1; +pub const MIN_SIGNERS: u8 = 1; /// Maximum number of multisignature signers (max N) -pub const MAX_SIGNERS: usize = 11; +pub const MAX_SIGNERS: u8 = 11; /// Multisignature data. #[repr(C)] @@ -21,12 +21,12 @@ pub struct Multisig { is_initialized: u8, /// Signer public keys - pub signers: [Pubkey; MAX_SIGNERS], + pub signers: [Pubkey; MAX_SIGNERS as usize], } impl Multisig { /// Utility function that checks index is between [`MIN_SIGNERS`] and [`MAX_SIGNERS`]. - pub fn is_valid_signer_index(index: usize) -> bool { + pub fn is_valid_signer_index(index: u8) -> bool { (MIN_SIGNERS..=MAX_SIGNERS).contains(&index) } @@ -36,7 +36,7 @@ impl Multisig { } } -impl RawType for Multisig { +impl Transmutable for Multisig { /// The length of the `Mint` account data. const LEN: usize = core::mem::size_of::(); } diff --git a/p-token/src/entrypoint.rs b/p-token/src/entrypoint.rs index 07808293..80fd04ea 100644 --- a/p-token/src/entrypoint.rs +++ b/p-token/src/entrypoint.rs @@ -2,6 +2,7 @@ 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::*; @@ -36,52 +37,53 @@ pub fn process_instruction( let (discriminator, instruction_data) = instruction_data .split_first() .ok_or(ProgramError::InvalidInstructionData)?; + let instruction = TokenInstruction::try_from(*discriminator)?; - match *discriminator { + match instruction { // 0 - InitializeMint - 0 => { + TokenInstruction::InitializeMint => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMint"); - process_initialize_mint(accounts, instruction_data, true) + process_initialize_mint(accounts, instruction_data) } // 3 - Transfer - 3 => { + TokenInstruction::Transfer => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Transfer"); process_transfer(accounts, instruction_data) } // 7 - MintTo - 7 => { + TokenInstruction::MintTo => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: MintTo"); process_mint_to(accounts, instruction_data) } // 9 - CloseAccount - 9 => { + TokenInstruction::CloseAccount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: CloseAccount"); process_close_account(accounts) } // 18 - InitializeAccount3 - 18 => { + TokenInstruction::InitializeAccount3 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeAccount3"); process_initialize_account3(accounts, instruction_data) } // 20 - InitializeMint2 - 20 => { + TokenInstruction::InitializeMint2 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMint2"); process_initialize_mint2(accounts, instruction_data) } - _ => process_remaining_instruction(accounts, instruction_data, *discriminator), + _ => process_remaining_instruction(accounts, instruction_data, instruction), } } @@ -93,137 +95,137 @@ pub fn process_instruction( fn process_remaining_instruction( accounts: &[AccountInfo], instruction_data: &[u8], - discriminator: u8, + instruction: TokenInstruction, ) -> ProgramResult { - match discriminator { + match instruction { // 1 - InitializeAccount - 1 => { + TokenInstruction::InitializeAccount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeAccount"); process_initialize_account(accounts) } // 2 - InitializeMultisig - 2 => { + TokenInstruction::InitializeMultisig => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMultisig"); process_initialize_multisig(accounts, instruction_data) } // 4 - Approve - 4 => { + TokenInstruction::Approve => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Approve"); process_approve(accounts, instruction_data) } // 5 - Revoke - 5 => { + TokenInstruction::Revoke => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Revoke"); process_revoke(accounts, instruction_data) } // 6 - SetAuthority - 6 => { + TokenInstruction::SetAuthority => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SetAuthority"); process_set_authority(accounts, instruction_data) } // 8 - Burn - 8 => { + TokenInstruction::Burn => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: Burn"); process_burn(accounts, instruction_data) } // 10 - FreezeAccount - 10 => { + TokenInstruction::FreezeAccount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: FreezeAccount"); process_freeze_account(accounts) } // 11 - ThawAccount - 11 => { + TokenInstruction::ThawAccount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ThawAccount"); process_thaw_account(accounts) } // 12 - TransferChecked - 12 => { + TokenInstruction::TransferChecked => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: TransferChecked"); process_transfer_checked(accounts, instruction_data) } // 13 - ApproveChecked - 13 => { + TokenInstruction::ApproveChecked => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: ApproveChecked"); process_approve_checked(accounts, instruction_data) } // 14 - MintToChecked - 14 => { + TokenInstruction::MintToChecked => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: MintToChecked"); process_mint_to_checked(accounts, instruction_data) } // 15 - BurnChecked - 15 => { + TokenInstruction::BurnChecked => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: BurnChecked"); process_burn_checked(accounts, instruction_data) } // 16 - InitializeAccount2 - 16 => { + TokenInstruction::InitializeAccount2 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeAccount2"); process_initialize_account2(accounts, instruction_data) } // 17 - SyncNative - 17 => { + TokenInstruction::SyncNative => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: SyncNative"); process_sync_native(accounts) } // 19 - InitializeMultisig2 - 19 => { + TokenInstruction::InitializeMultisig2 => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeMultisig2"); process_initialize_multisig2(accounts, instruction_data) } // 21 - GetAccountDataSize - 21 => { + TokenInstruction::GetAccountDataSize => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: GetAccountDataSize"); process_get_account_data_size(accounts) } // 22 - InitializeImmutableOwner - 22 => { + TokenInstruction::InitializeImmutableOwner => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: InitializeImmutableOwner"); process_initialize_immutable_owner(accounts) } // 23 - AmountToUiAmount - 23 => { + TokenInstruction::AmountToUiAmount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: AmountToUiAmount"); process_amount_to_ui_amount(accounts, instruction_data) } // 24 - UiAmountToAmount - 24 => { + TokenInstruction::UiAmountToAmount => { #[cfg(feature = "logging")] pinocchio::msg!("Instruction: UiAmountToAmount"); diff --git a/p-token/src/processor/approve_checked.rs b/p-token/src/processor/approve_checked.rs index 580a93d4..f9a82307 100644 --- a/p-token/src/processor/approve_checked.rs +++ b/p-token/src/processor/approve_checked.rs @@ -4,16 +4,16 @@ use super::shared; #[inline(always)] pub fn process_approve_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { - let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); - let amount = u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); + // expected u64 (8) + u8 (1) + let (amount, decimals) = if instruction_data.len() == 9 { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + ( + u64::from_le_bytes(amount.try_into().unwrap()), + decimals.first().copied(), + ) + } else { + return Err(ProgramError::InvalidInstructionData); + }; - shared::approve::process_approve( - accounts, - amount, - Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?), - ) + shared::approve::process_approve(accounts, amount, decimals) } diff --git a/p-token/src/processor/burn_checked.rs b/p-token/src/processor/burn_checked.rs index 88a745d3..2c8a1349 100644 --- a/p-token/src/processor/burn_checked.rs +++ b/p-token/src/processor/burn_checked.rs @@ -8,16 +8,12 @@ pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) - let (amount, decimals) = if instruction_data.len() == 9 { let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); ( - u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ), - decimals.first(), + u64::from_le_bytes(amount.try_into().unwrap()), + decimals.first().copied(), ) } else { return Err(ProgramError::InvalidInstructionData); }; - shared::burn::process_burn(accounts, amount, decimals.copied()) + shared::burn::process_burn(accounts, amount, decimals) } diff --git a/p-token/src/processor/close_account.rs b/p-token/src/processor/close_account.rs index 618b5677..21cfc4dc 100644 --- a/p-token/src/processor/close_account.rs +++ b/p-token/src/processor/close_account.rs @@ -1,19 +1,14 @@ -use pinocchio::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use spl_token_interface::{ error::TokenError, - state::{account::Account, load}, + state::{ + account::{Account, INCINERATOR_ID}, + load, + }, }; use super::validate_owner; -/// Incinerator (`1nc1nerator11111111111111111111111111111111`) address. -const INCINERATOR_ID: Pubkey = [ - 0, 51, 144, 114, 141, 52, 17, 96, 121, 189, 201, 17, 191, 255, 0, 219, 212, 77, 46, 205, 204, - 247, 156, 166, 225, 0, 56, 225, 0, 0, 0, 0, -]; - #[inline(always)] pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts diff --git a/p-token/src/processor/get_account_data_size.rs b/p-token/src/processor/get_account_data_size.rs index b7893178..690abf12 100644 --- a/p-token/src/processor/get_account_data_size.rs +++ b/p-token/src/processor/get_account_data_size.rs @@ -3,7 +3,7 @@ use pinocchio::{ }; use spl_token_interface::{ error::TokenError, - state::{account::Account, load, mint::Mint, RawType}, + state::{account::Account, load, mint::Mint, Transmutable}, }; use super::check_account_owner; diff --git a/p-token/src/processor/initialize_account2.rs b/p-token/src/processor/initialize_account2.rs index 185c291b..f28ec3ce 100644 --- a/p-token/src/processor/initialize_account2.rs +++ b/p-token/src/processor/initialize_account2.rs @@ -1,8 +1,5 @@ use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - pubkey::{Pubkey, PUBKEY_BYTES}, - ProgramResult, + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, }; use super::shared; @@ -12,13 +9,9 @@ pub fn process_initialize_account2( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - // SAFETY: validate `instruction_data` length. - let owner = unsafe { - if instruction_data.len() != PUBKEY_BYTES { - return Err(ProgramError::InvalidInstructionData); - } else { - &*(instruction_data.as_ptr() as *const Pubkey) - } - }; + let owner: &Pubkey = instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?; + shared::initialize_account::process_initialize_account(accounts, Some(owner), true) } diff --git a/p-token/src/processor/initialize_account3.rs b/p-token/src/processor/initialize_account3.rs index 54e55014..8aca4107 100644 --- a/p-token/src/processor/initialize_account3.rs +++ b/p-token/src/processor/initialize_account3.rs @@ -1,8 +1,5 @@ use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - pubkey::{Pubkey, PUBKEY_BYTES}, - ProgramResult, + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, }; use super::shared; @@ -12,13 +9,9 @@ pub fn process_initialize_account3( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - // SAFETY: validate `instruction_data` length. - let owner = unsafe { - if instruction_data.len() != PUBKEY_BYTES { - return Err(ProgramError::InvalidInstructionData); - } else { - &*(instruction_data.as_ptr() as *const Pubkey) - } - }; + let owner: &Pubkey = instruction_data + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?; + shared::initialize_account::process_initialize_account(accounts, Some(owner), false) } diff --git a/p-token/src/processor/initialize_immutable_owner.rs b/p-token/src/processor/initialize_immutable_owner.rs index 7a593364..db39cbd8 100644 --- a/p-token/src/processor/initialize_immutable_owner.rs +++ b/p-token/src/processor/initialize_immutable_owner.rs @@ -1,4 +1,4 @@ -use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use spl_token_interface::{ error::TokenError, state::{account::Account, load_unchecked, Initializable}, @@ -14,6 +14,6 @@ pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramRe if account.is_initialized() { return Err(TokenError::AlreadyInUse.into()); } - msg!("Please upgrade to SPL Token 2022 for immutable owner support"); + // Please upgrade to SPL Token 2022 for immutable owner support. Ok(()) } diff --git a/p-token/src/processor/initialize_mint.rs b/p-token/src/processor/initialize_mint.rs index 935acfd1..3d5b5f5c 100644 --- a/p-token/src/processor/initialize_mint.rs +++ b/p-token/src/processor/initialize_mint.rs @@ -1,120 +1,8 @@ -use core::{marker::PhantomData, mem::size_of}; -use pinocchio::{ - account_info::AccountInfo, - program_error::ProgramError, - pubkey::Pubkey, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; -use spl_token_interface::{ - error::TokenError, - state::{load_mut_unchecked, mint::Mint, Initializable}, -}; +use pinocchio::{account_info::AccountInfo, ProgramResult}; -#[inline(always)] -pub fn process_initialize_mint( - accounts: &[AccountInfo], - instruction_data: &[u8], - rent_sysvar_account: bool, -) -> ProgramResult { - // Validates the instruction data. - - let args = InitializeMint::try_from_bytes(instruction_data)?; - - // Validates the accounts. - - let (mint_info, rent_sysvar_info) = if rent_sysvar_account { - let [mint_info, rent_sysvar_info, _remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (mint_info, Some(rent_sysvar_info)) - } else { - let [mint_info, _remaining @ ..] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - (mint_info, None) - }; - - // SAFETY: single mutable borrow to `mint_info` account data. - let mint = unsafe { load_mut_unchecked::(mint_info.borrow_mut_data_unchecked())? }; - - if mint.is_initialized() { - return Err(TokenError::AlreadyInUse.into()); - } - - // Check rent-exempt status of the mint account. - - let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are - // checked by `from_account_info_unchecked`. - let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; - rent.is_exempt(mint_info.lamports(), size_of::()) - } else { - Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) - }; - - if !is_exempt { - return Err(TokenError::NotRentExempt.into()); - } - - // Initialize the mint. - - mint.set_initialized(true); - mint.set_mint_authority(args.mint_authority()); - mint.decimals = args.decimals(); +use super::shared; - if let Some(freeze_authority) = args.freeze_authority() { - mint.set_freeze_authority(freeze_authority); - } - - Ok(()) -} - -/// Instruction data for the `InitializeMint` instruction. -pub struct InitializeMint<'a> { - raw: *const u8, - - _data: PhantomData<&'a [u8]>, -} - -impl InitializeMint<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result { - // The minimum expected size of the instruction data. - // - decimals (1 byte) - // - mint_authority (32 bytes) - // - option + freeze_authority (1 byte + 32 bytes) - if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(InitializeMint { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn decimals(&self) -> u8 { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { *self.raw } - } - - #[inline] - pub fn mint_authority(&self) -> &Pubkey { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { &*(self.raw.add(1) as *const Pubkey) } - } - - #[inline] - pub fn freeze_authority(&self) -> Option<&Pubkey> { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { - if *self.raw.add(33) == 0 { - Option::None - } else { - Option::Some(&*(self.raw.add(34) as *const Pubkey)) - } - } - } +#[inline(always)] +pub fn process_initialize_mint(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { + shared::initialize_mint::process_initialize_mint(accounts, instruction_data, true) } diff --git a/p-token/src/processor/initialize_mint2.rs b/p-token/src/processor/initialize_mint2.rs index 0f1f07d2..41b2c0b8 100644 --- a/p-token/src/processor/initialize_mint2.rs +++ b/p-token/src/processor/initialize_mint2.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, ProgramResult}; -use super::initialize_mint::process_initialize_mint; +use super::shared::initialize_mint::process_initialize_mint; #[inline(always)] pub fn process_initialize_mint2( diff --git a/p-token/src/processor/mint_to_checked.rs b/p-token/src/processor/mint_to_checked.rs index ebfcdea4..2386bf52 100644 --- a/p-token/src/processor/mint_to_checked.rs +++ b/p-token/src/processor/mint_to_checked.rs @@ -8,16 +8,12 @@ pub fn process_mint_to_checked(accounts: &[AccountInfo], instruction_data: &[u8] let (amount, decimals) = if instruction_data.len() == 9 { let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); ( - u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ), - decimals.first(), + u64::from_le_bytes(amount.try_into().unwrap()), + decimals.first().copied(), ) } else { return Err(ProgramError::InvalidInstructionData); }; - shared::mint_to::process_mint_to(accounts, amount, decimals.copied()) + shared::mint_to::process_mint_to(accounts, amount, decimals) } diff --git a/p-token/src/processor/mod.rs b/p-token/src/processor/mod.rs index ee807d42..e4e2e024 100644 --- a/p-token/src/processor/mod.rs +++ b/p-token/src/processor/mod.rs @@ -1,5 +1,4 @@ use core::{ - cmp::max, mem::MaybeUninit, slice::{from_raw_parts, from_raw_parts_mut}, str::from_utf8_unchecked, @@ -14,7 +13,7 @@ use spl_token_interface::{ state::{ load, multisig::{Multisig, MAX_SIGNERS}, - RawType, + Transmutable, }, }; @@ -95,7 +94,8 @@ fn check_account_owner(account_info: &AccountInfo) -> ProgramResult { /// Validates owner(s) are present. /// /// Note that `owner_account_info` will be immutable borrowed when it represents -/// a multisig account. +/// a multisig account, therefore it should not have any mutable borrows when +/// calling this function. #[inline(always)] fn validate_owner( expected_owner: &Pubkey, @@ -110,11 +110,13 @@ fn validate_owner( && owner_account_info.owner() == &TOKEN_PROGRAM_ID { // SAFETY: the caller guarantees that there are no mutable borrows of `owner_account_info` - // account data and the `load` validates that the account is initialized. + // account data and the `load` validates that the account is initialized; additionally, + // `Multisig` accounts are only ever loaded in this function, which means that previous + // loads will have already failed by the time we get here. let multisig = unsafe { load::(owner_account_info.borrow_data_unchecked())? }; let mut num_signers = 0; - let mut matched = [false; MAX_SIGNERS]; + let mut matched = [false; MAX_SIGNERS as usize]; for signer in signers.iter() { for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() { @@ -152,13 +154,12 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result decimals - || (length + expected_after_decimal_length) > MAX_FORMATTED_DIGITS + || (length + decimals) > MAX_FORMATTED_DIGITS { return Err(ProgramError::InvalidArgument); } @@ -170,7 +171,7 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result Result`, which has the same memory @@ -195,11 +196,9 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result() .map_err(|_| ProgramError::InvalidArgument) } diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index 5da19c2e..9577aba6 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -6,7 +6,7 @@ use pinocchio::{ use spl_token_interface::{ error::TokenError, instruction::AuthorityType, - state::{account::Account, load_mut, mint::Mint, RawType}, + state::{account::Account, load_mut, mint::Mint, Transmutable}, }; use super::validate_owner; @@ -17,9 +17,6 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) let args = SetAuthority::try_from_bytes(instruction_data)?; - let authority_type = args.authority_type()?; - let new_authority = args.new_authority(); - // Validates the accounts. let [account_info, authority_info, remaining @ ..] = accounts else { @@ -35,11 +32,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()); @@ -56,7 +53,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(); @@ -71,7 +68,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. @@ -79,7 +76,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(); @@ -94,7 +91,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(); @@ -112,7 +109,9 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) } struct SetAuthority<'a> { - raw: *const u8, + authority_type: AuthorityType, + + new_authority: Option<&'a Pubkey>, _data: PhantomData<&'a [u8]>, } @@ -120,33 +119,22 @@ struct SetAuthority<'a> { impl SetAuthority<'_> { #[inline(always)] pub fn try_from_bytes(bytes: &[u8]) -> Result { - // The minimum expected size of the instruction data. - // - authority_type (1 byte) - // - option + new_authority (1 byte + 32 bytes) - if bytes.len() < 2 || (bytes[1] == 1 && bytes.len() < 34) { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(SetAuthority { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline(always)] - pub fn authority_type(&self) -> Result { - // SAFETY: `bytes` length is validated in `try_from_bytes`. - unsafe { AuthorityType::from(*self.raw) } - } - - #[inline(always)] - pub fn new_authority(&self) -> Option<&Pubkey> { - // SAFETY: `bytes` length is validated in `try_from_bytes`. + // The minimum expected size of the instruction data is either 2 or 34 bytes: + // - authority_type (1 byte) + // - option + new_authority (1 byte + 32 bytes) unsafe { - if *self.raw.add(1) == 0 { - Option::None - } else { - Option::Some(&*(self.raw.add(2) as *const Pubkey)) + match bytes.len() { + 2 if *bytes.get_unchecked(1) == 0 => Ok(SetAuthority { + authority_type: AuthorityType::from(*bytes.get_unchecked(0))?, + new_authority: None, + _data: PhantomData, + }), + 34 if *bytes.get_unchecked(1) == 1 => Ok(SetAuthority { + authority_type: AuthorityType::from(*bytes.get_unchecked(0))?, + new_authority: Some(&*(bytes.as_ptr().add(2) as *const Pubkey)), + _data: PhantomData, + }), + _ => Err(ProgramError::InvalidInstructionData), } } } diff --git a/p-token/src/processor/shared/burn.rs b/p-token/src/processor/shared/burn.rs index cae712ef..34f30aa5 100644 --- a/p-token/src/processor/shared/burn.rs +++ b/p-token/src/processor/shared/burn.rs @@ -36,7 +36,9 @@ pub fn process_burn( .ok_or(TokenError::InsufficientFunds)?; // SAFETY: single mutable borrow to `mint_info` account data and - // `load_mut` validates that the mint is initialized. + // `load_mut` validates that the mint is initialized; additionally, an + // account cannot be both a token account and a mint, so if duplicates are + // passed in, one of them will fail the `load_mut` check. let mint = unsafe { load_mut::(mint_info.borrow_mut_data_unchecked())? }; if mint_info.key() != &source_account.mint { @@ -78,10 +80,7 @@ pub fn process_burn( } else { source_account.set_amount(updated_source_amount); - let mint_supply = mint - .supply() - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; + let mint_supply = mint.supply().checked_sub(amount).unwrap(); mint.set_supply(mint_supply); } diff --git a/p-token/src/processor/shared/initialize_account.rs b/p-token/src/processor/shared/initialize_account.rs index 02fcacd2..b87d0346 100644 --- a/p-token/src/processor/shared/initialize_account.rs +++ b/p-token/src/processor/shared/initialize_account.rs @@ -20,7 +20,7 @@ use crate::processor::check_account_owner; pub fn process_initialize_account( accounts: &[AccountInfo], owner: Option<&Pubkey>, - rent_sysvar_account: bool, + rent_sysvar_account_provided: bool, ) -> ProgramResult { // Accounts expected depend on whether we have the `rent_sysvar` account or not. @@ -40,7 +40,7 @@ pub fn process_initialize_account( let new_account_info_data_len = new_account_info.data_len(); - let minimum_balance = if rent_sysvar_account { + let minimum_balance = if rent_sysvar_account_provided { let rent_sysvar_info = remaining .first() .ok_or(ProgramError::NotEnoughAccountKeys)?; @@ -85,15 +85,9 @@ pub fn process_initialize_account( if is_native_mint { account.set_native(true); account.set_native_amount(minimum_balance); - // SAFETY: single mutable borrow to `new_account_info` lamports. - unsafe { - account.set_amount( - new_account_info - .borrow_lamports_unchecked() - .checked_sub(minimum_balance) - .ok_or(TokenError::Overflow)?, - ); - } + // `new_account_info` lamports are already checked to be greater than or equal + // to the minimum balance. + account.set_amount(new_account_info.lamports() - minimum_balance); } Ok(()) diff --git a/p-token/src/processor/shared/initialize_mint.rs b/p-token/src/processor/shared/initialize_mint.rs new file mode 100644 index 00000000..8d6afe25 --- /dev/null +++ b/p-token/src/processor/shared/initialize_mint.rs @@ -0,0 +1,120 @@ +use core::{marker::PhantomData, mem::size_of}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::Pubkey, + sysvars::{rent::Rent, Sysvar}, + ProgramResult, +}; +use spl_token_interface::{ + error::TokenError, + state::{load_mut_unchecked, mint::Mint, Initializable}, +}; + +#[inline(always)] +pub fn process_initialize_mint( + accounts: &[AccountInfo], + instruction_data: &[u8], + rent_sysvar_account_provided: bool, +) -> ProgramResult { + // Validates the instruction data. + + let args = InitializeMint::try_from_bytes(instruction_data)?; + + // Validates the accounts. + + let (mint_info, rent_sysvar_info) = if rent_sysvar_account_provided { + let [mint_info, rent_sysvar_info, _remaining @ ..] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + (mint_info, Some(rent_sysvar_info)) + } else { + let [mint_info, _remaining @ ..] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + (mint_info, None) + }; + + // SAFETY: single mutable borrow to `mint_info` account data. + let mint = unsafe { load_mut_unchecked::(mint_info.borrow_mut_data_unchecked())? }; + + if mint.is_initialized() { + return Err(TokenError::AlreadyInUse.into()); + } + + // Check rent-exempt status of the mint account. + + let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { + // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are + // checked by `from_account_info_unchecked`. + let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; + rent.is_exempt(mint_info.lamports(), size_of::()) + } else { + Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) + }; + + if !is_exempt { + return Err(TokenError::NotRentExempt.into()); + } + + // Initialize the mint. + + mint.set_initialized(); + mint.set_mint_authority(args.mint_authority()); + mint.decimals = args.decimals(); + + if let Some(freeze_authority) = args.freeze_authority() { + mint.set_freeze_authority(freeze_authority); + } + + Ok(()) +} + +/// Instruction data for the `InitializeMint` instruction. +pub struct InitializeMint<'a> { + raw: *const u8, + + _data: PhantomData<&'a [u8]>, +} + +impl InitializeMint<'_> { + #[inline] + pub fn try_from_bytes(bytes: &[u8]) -> Result { + // The minimum expected size of the instruction data. + // - decimals (1 byte) + // - mint_authority (32 bytes) + // - option + freeze_authority (1 byte + 32 bytes) + if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(InitializeMint { + raw: bytes.as_ptr(), + _data: PhantomData, + }) + } + + #[inline] + pub fn decimals(&self) -> u8 { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. + unsafe { *self.raw } + } + + #[inline] + pub fn mint_authority(&self) -> &Pubkey { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. + unsafe { &*(self.raw.add(1) as *const Pubkey) } + } + + #[inline] + pub fn freeze_authority(&self) -> Option<&Pubkey> { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. + unsafe { + if *self.raw.add(33) == 0 { + Option::None + } else { + Option::Some(&*(self.raw.add(34) as *const Pubkey)) + } + } + } +} diff --git a/p-token/src/processor/shared/initialize_multisig.rs b/p-token/src/processor/shared/initialize_multisig.rs index fc863fbf..00480ff7 100644 --- a/p-token/src/processor/shared/initialize_multisig.rs +++ b/p-token/src/processor/shared/initialize_multisig.rs @@ -13,11 +13,11 @@ use spl_token_interface::{ pub fn process_initialize_multisig( accounts: &[AccountInfo], m: u8, - rent_sysvar_account: bool, + rent_sysvar_account_provided: bool, ) -> ProgramResult { // Accounts expected depend on whether we have the `rent_sysvar` account or not. - let (multisig_info, rent_sysvar_info, remaining) = if rent_sysvar_account { + let (multisig_info, rent_sysvar_info, remaining) = if rent_sysvar_account_provided { let [multisig_info, rent_sysvar_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -57,10 +57,10 @@ pub fn process_initialize_multisig( multisig.m = m; multisig.n = remaining.len() as u8; - if !Multisig::is_valid_signer_index(multisig.n as usize) { + if !Multisig::is_valid_signer_index(multisig.n) { return Err(TokenError::InvalidNumberOfProvidedSigners.into()); } - if !Multisig::is_valid_signer_index(multisig.m as usize) { + if !Multisig::is_valid_signer_index(multisig.m) { return Err(TokenError::InvalidNumberOfRequiredSigners.into()); } diff --git a/p-token/src/processor/shared/mint_to.rs b/p-token/src/processor/shared/mint_to.rs index 3548cc4e..14aabbae 100644 --- a/p-token/src/processor/shared/mint_to.rs +++ b/p-token/src/processor/shared/mint_to.rs @@ -51,20 +51,19 @@ pub fn process_mint_to( } if amount == 0 { + // Validates the accounts' owner since we are not writing + // to these account. check_account_owner(mint_info)?; check_account_owner(destination_account_info)?; } else { - let destination_amount = destination_account - .amount() - .checked_add(amount) - .ok_or(TokenError::Overflow)?; - destination_account.set_amount(destination_amount); - let mint_supply = mint .supply() .checked_add(amount) .ok_or(TokenError::Overflow)?; mint.set_supply(mint_supply); + + // This should not fail since there is no overflow on the mint supply. + destination_account.set_amount(destination_account.amount() + amount); } Ok(()) diff --git a/p-token/src/processor/shared/mod.rs b/p-token/src/processor/shared/mod.rs index e55f1298..9569cdf6 100644 --- a/p-token/src/processor/shared/mod.rs +++ b/p-token/src/processor/shared/mod.rs @@ -6,6 +6,7 @@ pub mod approve; pub mod burn; pub mod initialize_account; +pub mod initialize_mint; pub mod initialize_multisig; pub mod mint_to; pub mod toggle_account_state; diff --git a/p-token/src/processor/shared/toggle_account_state.rs b/p-token/src/processor/shared/toggle_account_state.rs index ba1b7aaf..66838198 100644 --- a/p-token/src/processor/shared/toggle_account_state.rs +++ b/p-token/src/processor/shared/toggle_account_state.rs @@ -28,7 +28,9 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P } // SAFETY: single immutable borrow of `mint_info` account data and - // `load` validates that the mint is initialized. + // `load` validates that the mint is initialized; additionally, an + // account cannot be both a token account and a mint, so if duplicates are + // passed in, one of them will fail the `load` check. let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; match mint.freeze_authority() { diff --git a/p-token/src/processor/shared/transfer.rs b/p-token/src/processor/shared/transfer.rs index 84ae7cfc..b98e7be6 100644 --- a/p-token/src/processor/shared/transfer.rs +++ b/p-token/src/processor/shared/transfer.rs @@ -64,6 +64,16 @@ pub fn process_transfer( // Implicitly validates that the account has enough tokens by calculating the // remaining amount - the amount is only updated on the account if the transfer // is successful. + // + // Note: the logic is partially duplicated for self transfers and transfers + // to different accounts to improve CU consumption: + // + // - self-transfer: we only need to check that the source account is not frozen + // and has enough tokens. + // + // - transfers to different accounts: we need to check that the source and + // destination accounts are not frozen, have the same mint, and the source + // account has enough tokens. let remaining_amount = if self_transfer { if source_account.is_frozen() { return Err(TokenError::AccountFrozen.into()); @@ -75,7 +85,8 @@ pub fn process_transfer( .ok_or(TokenError::InsufficientFunds)? } else { // SAFETY: scoped immutable borrow to `destination_account_info` account data and - // `load` validates that the account is initialized. + // `load` validates that the account is initialized; additionally, the account + // is guaranteed to be different than `source_account_info`. let destination_account = unsafe { load::(destination_account_info.borrow_data_unchecked())? }; @@ -143,15 +154,12 @@ pub fn process_transfer( source_account.set_amount(remaining_amount); // SAFETY: single mutable borrow to `destination_account_info` account data; the account - // is guaranteed to be initialized and different than `source_account_info`. + // is guaranteed to be initialized and different than `source_account_info`; it was + // also already validated to be a token account. let destination_account = unsafe { load_mut_unchecked::(destination_account_info.borrow_mut_data_unchecked())? }; - let destination_amount = destination_account - .amount() - .checked_add(amount) - .ok_or(TokenError::Overflow)?; - destination_account.set_amount(destination_amount); + destination_account.set_amount(destination_account.amount() + amount); if source_account.is_native() { // SAFETY: single mutable borrow to `source_account_info` lamports. @@ -160,7 +168,8 @@ pub fn process_transfer( .checked_sub(amount) .ok_or(TokenError::Overflow)?; - // SAFETY: single mutable borrow to `destination_account_info` lamports. + // SAFETY: single mutable borrow to `destination_account_info` lamports; the account + // is already validated to be different from `source_account_info`. let destination_lamports = unsafe { destination_account_info.borrow_mut_lamports_unchecked() }; *destination_lamports = destination_lamports diff --git a/p-token/src/processor/transfer_checked.rs b/p-token/src/processor/transfer_checked.rs index ea75a289..b688cd4b 100644 --- a/p-token/src/processor/transfer_checked.rs +++ b/p-token/src/processor/transfer_checked.rs @@ -11,16 +11,12 @@ pub fn process_transfer_checked( let (amount, decimals) = if instruction_data.len() == 9 { let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); ( - u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ), - decimals.first(), + u64::from_le_bytes(amount.try_into().unwrap()), + decimals.first().copied(), ) } else { return Err(ProgramError::InvalidInstructionData); }; - shared::transfer::process_transfer(accounts, amount, decimals.copied()) + shared::transfer::process_transfer(accounts, amount, decimals) } From 4e6535ce44cc87b2dc37f6c9e2068f48269bb64c Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 6 Mar 2025 00:09:55 +0000 Subject: [PATCH 5/7] [wip]: More fixes --- interface/src/instruction.rs | 18 +++----- p-token/src/processor/mod.rs | 42 +++++-------------- p-token/src/processor/set_authority.rs | 4 +- .../src/processor/shared/initialize_mint.rs | 8 ++-- 4 files changed, 21 insertions(+), 51 deletions(-) diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index ac8d8f30..1d75cfc0 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -2,8 +2,6 @@ use pinocchio::program_error::ProgramError; -use crate::error::TokenError; - /// Instructions supported by the token program. #[repr(u8)] #[derive(Clone, Debug, PartialEq)] @@ -510,21 +508,15 @@ pub enum AuthorityType { CloseAccount, } -impl AuthorityType { - pub fn into(&self) -> u8 { - match self { - AuthorityType::MintTokens => 0, - AuthorityType::FreezeAccount => 1, - AuthorityType::AccountOwner => 2, - AuthorityType::CloseAccount => 3, - } - } +impl TryFrom for AuthorityType { + type Error = ProgramError; #[inline(always)] - pub fn from(value: u8) -> Result { + fn try_from(value: u8) -> Result { match value { + // SAFETY: `value` is guaranteed to be in the range of the enum variants. 0..=3 => Ok(unsafe { core::mem::transmute::(value) }), - _ => Err(TokenError::InvalidInstruction.into()), + _ => Err(ProgramError::InvalidInstructionData), } } } diff --git a/p-token/src/processor/mod.rs b/p-token/src/processor/mod.rs index e4e2e024..782f7bd5 100644 --- a/p-token/src/processor/mod.rs +++ b/p-token/src/processor/mod.rs @@ -1,10 +1,6 @@ -use core::{ - mem::MaybeUninit, - slice::{from_raw_parts, from_raw_parts_mut}, - str::from_utf8_unchecked, -}; +use core::{slice::from_raw_parts, str::from_utf8_unchecked}; use pinocchio::{ - account_info::AccountInfo, memory::sol_memcpy, program_error::ProgramError, pubkey::Pubkey, + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, syscalls::sol_memcpy_, ProgramResult, }; use spl_token_interface::{ @@ -71,9 +67,6 @@ pub use transfer::process_transfer; pub use transfer_checked::process_transfer_checked; pub use ui_amount_to_amount::process_ui_amount_to_amount; -/// An uninitialized byte. -const UNINIT_BYTE: MaybeUninit = MaybeUninit::uninit(); - /// Maximum number of digits in a formatted `u64`. /// /// The maximum number of digits is equal to the maximum number @@ -164,41 +157,26 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result`, which has the same - // memory layout as `u8`. - let slice: &mut [u8] = - unsafe { from_raw_parts_mut(digits.as_mut_ptr() as *mut _, MAX_FORMATTED_DIGITS) }; + let mut digits = [b'0'; MAX_FORMATTED_DIGITS]; // SAFETY: the total length of `amount_str` and `after_decimal` is less than // `MAX_FORMATTED_DIGITS`. unsafe { - sol_memcpy(slice, amount_str.as_bytes(), length); + sol_memcpy_(digits.as_mut_ptr(), amount_str.as_ptr(), length as u64); - sol_memcpy( - &mut slice[length..], - after_decimal.as_bytes(), - after_decimal.len(), + sol_memcpy_( + digits.as_mut_ptr().add(length), + after_decimal.as_ptr(), + after_decimal.len() as u64, ); } - let length = amount_str.len() + after_decimal.len(); let remaining = decimals.saturating_sub(after_decimal.len()); - - // SAFETY: `digits` is an array of `MaybeUninit`, which has the same memory - // layout as `u8`. - let ptr = unsafe { digits.as_mut_ptr().add(length) }; - - for offset in 0..remaining { - // SAFETY: `ptr` is within the bounds of `digits`. - unsafe { - (ptr.add(offset) as *mut u8).write(b'0'); - } - } + let length = amount_str.len() + after_decimal.len() + remaining; // SAFETY: `digits` only contains valid UTF-8 bytes. unsafe { - from_utf8_unchecked(from_raw_parts(digits.as_ptr() as _, length + remaining)) + from_utf8_unchecked(from_raw_parts(digits.as_ptr(), length)) .parse::() .map_err(|_| ProgramError::InvalidArgument) } diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index 9577aba6..c2fa88ea 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -125,12 +125,12 @@ impl SetAuthority<'_> { unsafe { match bytes.len() { 2 if *bytes.get_unchecked(1) == 0 => Ok(SetAuthority { - authority_type: AuthorityType::from(*bytes.get_unchecked(0))?, + authority_type: (*bytes.get_unchecked(0)).try_into()?, new_authority: None, _data: PhantomData, }), 34 if *bytes.get_unchecked(1) == 1 => Ok(SetAuthority { - authority_type: AuthorityType::from(*bytes.get_unchecked(0))?, + authority_type: (*bytes.get_unchecked(0)).try_into()?, new_authority: Some(&*(bytes.as_ptr().add(2) as *const Pubkey)), _data: PhantomData, }), diff --git a/p-token/src/processor/shared/initialize_mint.rs b/p-token/src/processor/shared/initialize_mint.rs index 8d6afe25..0e9eec8e 100644 --- a/p-token/src/processor/shared/initialize_mint.rs +++ b/p-token/src/processor/shared/initialize_mint.rs @@ -80,10 +80,10 @@ pub struct InitializeMint<'a> { impl InitializeMint<'_> { #[inline] pub fn try_from_bytes(bytes: &[u8]) -> Result { - // The minimum expected size of the instruction data. - // - decimals (1 byte) - // - mint_authority (32 bytes) - // - option + freeze_authority (1 byte + 32 bytes) + // The minimum expected size of the instruction data is either 34 or 66 bytes: + // - decimals (1 byte) + // - mint_authority (32 bytes) + // - option + freeze_authority (1 byte + 32 bytes) if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) { return Err(ProgramError::InvalidInstructionData); } From 0fee8f1952e012c93b90cbd16fdc7e925dc377bf Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 6 Mar 2025 19:31:56 +0000 Subject: [PATCH 6/7] A few more fixes --- Cargo.lock | 35 ++++++++-- interface/Cargo.toml | 4 ++ interface/src/instruction.rs | 66 ++++++++++++++----- p-token/src/processor/mod.rs | 3 +- p-token/src/processor/set_authority.rs | 55 ++++++++-------- p-token/src/processor/shared/burn.rs | 3 +- .../src/processor/shared/initialize_mint.rs | 57 +++++++--------- p-token/src/processor/shared/transfer.rs | 2 + 8 files changed, 141 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c75292cd..f30c2e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1638,6 +1638,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -5666,8 +5672,8 @@ dependencies = [ "solana-vote", "solana-vote-program", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "symlink", "tar", "tempfile", @@ -6784,6 +6790,8 @@ version = "0.0.0" dependencies = [ "pinocchio", "pinocchio-pubkey", + "strum 0.27.1", + "strum_macros 0.27.1", ] [[package]] @@ -6810,22 +6818,41 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", ] +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" + [[package]] name = "strum_macros" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.96", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/interface/Cargo.toml b/interface/Cargo.toml index 2e3ae9e6..69118d03 100644 --- a/interface/Cargo.toml +++ b/interface/Cargo.toml @@ -14,3 +14,7 @@ crate-type = ["rlib"] [dependencies] pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } pinocchio-pubkey = { version = "0.2", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } + +[dev-dependencies] +strum = "0.27" +strum_macros = "0.27" diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index 1d75cfc0..e0f747e1 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -5,6 +5,7 @@ use pinocchio::program_error::ProgramError; /// Instructions supported by the token program. #[repr(u8)] #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))] pub enum TokenInstruction { /// Initializes a new mint and optionally deposits all the newly minted /// tokens in an account. @@ -20,7 +21,7 @@ pub enum TokenInstruction { /// 0. `[writable]` The mint to initialize. /// 1. `[]` Rent sysvar. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u8` The number of base 10 digits to the right of the decimal place. /// - `Pubkey` The authority/multisignature to mint tokens. @@ -66,7 +67,7 @@ pub enum TokenInstruction { /// 1. `[]` Rent sysvar. /// 2. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u8` The number of signers (M) required to validate this multisignature account. InitializeMultisig, @@ -89,7 +90,7 @@ pub enum TokenInstruction { /// 2. `[]` The source account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens to transfer. Transfer, @@ -110,7 +111,7 @@ pub enum TokenInstruction { /// 2. `[]` The source account's multisignature owner. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens the delegate is approved for. Approve, @@ -142,7 +143,7 @@ pub enum TokenInstruction { /// 1. `[]` The mint's or account's current multisignature authority. /// 2. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `AuthorityType` The type of authority to update. /// - `Option` The new authority. @@ -164,7 +165,7 @@ pub enum TokenInstruction { /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of new tokens to mint. MintTo, @@ -185,7 +186,7 @@ pub enum TokenInstruction { /// 2. `[]` The account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens to burn. Burn, @@ -264,7 +265,7 @@ pub enum TokenInstruction { /// 3. `[]` The source account's multisignature owner/delegate. /// 4. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens to transfer. /// - `u8` Expected number of base 10 digits to the right of the decimal place. @@ -292,7 +293,7 @@ pub enum TokenInstruction { /// 3. `[]` The source account's multisignature owner. /// 4. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens the delegate is approved for. /// - `u8` Expected number of base 10 digits to the right of the decimal place. @@ -318,7 +319,7 @@ pub enum TokenInstruction { /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of new tokens to mint. /// - `u8` Expected number of base 10 digits to the right of the decimal place. @@ -345,7 +346,7 @@ pub enum TokenInstruction { /// 2. `[]` The account's multisignature owner/delegate. /// 3. `..+M` `[signer]` M signer accounts. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens to burn. /// - `u8` Expected number of base 10 digits to the right of the decimal place. @@ -362,7 +363,7 @@ pub enum TokenInstruction { /// 1. `[]` The mint this account will be associated with. /// 2. `[]` Rent sysvar. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `Pubkey` The new account's owner/multisignature. InitializeAccount2, @@ -387,7 +388,7 @@ pub enum TokenInstruction { /// 0. `[writable]` The account to initialize. /// 1. `[]` The mint this account will be associated with. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `Pubkey` The new account's owner/multisignature. InitializeAccount3, @@ -400,7 +401,7 @@ pub enum TokenInstruction { /// 0. `[writable]` The multisignature account to initialize. /// 1. `..+N` `[signer]` The signer accounts, must equal to N where `1 <= N <= 11`. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u8` The number of signers (M) required to validate this multisignature account. InitializeMultisig2, @@ -412,7 +413,7 @@ pub enum TokenInstruction { /// /// 0. `[writable]` The mint to initialize. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u8` The number of base 10 digits to the right of the decimal place. /// - `Pubkey` The authority/multisignature to mint tokens. @@ -456,7 +457,7 @@ pub enum TokenInstruction { /// /// 0. `[]` The mint to calculate for /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `u64` The amount of tokens to reformat. AmountToUiAmount, @@ -472,7 +473,7 @@ pub enum TokenInstruction { /// /// 0. `[]` The mint to calculate for. /// - /// Instructions data expected by this instruction: + /// Data expected by this instruction: /// /// - `&str` The `ui_amount` of tokens to reformat. UiAmountToAmount, @@ -497,6 +498,7 @@ impl TryFrom for TokenInstruction { /// Specifies the authority type for `SetAuthority` instructions #[repr(u8)] #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))] pub enum AuthorityType { /// Authority to mint new tokens MintTokens, @@ -520,3 +522,33 @@ impl TryFrom for AuthorityType { } } } + +#[cfg(test)] +mod tests { + use super::{AuthorityType, TokenInstruction}; + use strum::IntoEnumIterator; + + #[test] + fn test_token_instruction_from_u8_exhaustive() { + for variant in TokenInstruction::iter() { + let variant_u8 = variant.clone() as u8; + assert_eq!( + TokenInstruction::from_repr(variant_u8), + Some(TokenInstruction::try_from(variant_u8).unwrap()) + ); + assert_eq!(TokenInstruction::try_from(variant_u8).unwrap(), variant); + } + } + + #[test] + fn test_authority_type_from_u8_exhaustive() { + for variant in AuthorityType::iter() { + let variant_u8 = variant.clone() as u8; + assert_eq!( + AuthorityType::from_repr(variant_u8), + Some(AuthorityType::try_from(variant_u8).unwrap()) + ); + assert_eq!(AuthorityType::try_from(variant_u8).unwrap(), variant); + } + } +} diff --git a/p-token/src/processor/mod.rs b/p-token/src/processor/mod.rs index 782f7bd5..96e7586d 100644 --- a/p-token/src/processor/mod.rs +++ b/p-token/src/processor/mod.rs @@ -171,8 +171,7 @@ fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result { 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()); @@ -53,7 +51,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(); @@ -68,7 +66,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. @@ -76,7 +74,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(); @@ -91,7 +89,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(); @@ -108,34 +106,39 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) Ok(()) } -struct SetAuthority<'a> { - authority_type: AuthorityType, - - new_authority: Option<&'a Pubkey>, +#[repr(C)] +struct SetAuthority { + authority_type: u8, - _data: PhantomData<&'a [u8]>, + new_authority: (u8, Pubkey), } -impl SetAuthority<'_> { - #[inline(always)] - pub fn try_from_bytes(bytes: &[u8]) -> Result { +impl SetAuthority { + #[inline] + pub fn try_from_bytes(bytes: &[u8]) -> Result<&SetAuthority, ProgramError> { // The minimum expected size of the instruction data is either 2 or 34 bytes: // - authority_type (1 byte) // - option + new_authority (1 byte + 32 bytes) unsafe { match bytes.len() { - 2 if *bytes.get_unchecked(1) == 0 => Ok(SetAuthority { - authority_type: (*bytes.get_unchecked(0)).try_into()?, - new_authority: None, - _data: PhantomData, - }), - 34 if *bytes.get_unchecked(1) == 1 => Ok(SetAuthority { - authority_type: (*bytes.get_unchecked(0)).try_into()?, - new_authority: Some(&*(bytes.as_ptr().add(2) as *const Pubkey)), - _data: PhantomData, - }), + 2 if *bytes.get_unchecked(1) == 0 => Ok(&*(bytes.as_ptr() as *const SetAuthority)), + 34 => Ok(&*(bytes.as_ptr() as *const SetAuthority)), _ => Err(ProgramError::InvalidInstructionData), } } } + + #[inline] + pub fn authority_type(&self) -> Result { + self.authority_type.try_into() + } + + #[inline] + pub fn new_authority(&self) -> Option<&Pubkey> { + if self.new_authority.0 == 0 { + Option::None + } else { + Option::Some(&self.new_authority.1) + } + } } diff --git a/p-token/src/processor/shared/burn.rs b/p-token/src/processor/shared/burn.rs index 34f30aa5..abb250aa 100644 --- a/p-token/src/processor/shared/burn.rs +++ b/p-token/src/processor/shared/burn.rs @@ -79,7 +79,8 @@ pub fn process_burn( check_account_owner(mint_info)?; } else { source_account.set_amount(updated_source_amount); - + // Note: The amount of a token account is always within the range of the + // mint supply (`u64`). let mint_supply = mint.supply().checked_sub(amount).unwrap(); mint.set_supply(mint_supply); } diff --git a/p-token/src/processor/shared/initialize_mint.rs b/p-token/src/processor/shared/initialize_mint.rs index 0e9eec8e..4804f0c8 100644 --- a/p-token/src/processor/shared/initialize_mint.rs +++ b/p-token/src/processor/shared/initialize_mint.rs @@ -1,4 +1,4 @@ -use core::{marker::PhantomData, mem::size_of}; +use core::mem::size_of; use pinocchio::{ account_info::AccountInfo, program_error::ProgramError, @@ -60,8 +60,8 @@ pub fn process_initialize_mint( // Initialize the mint. mint.set_initialized(); - mint.set_mint_authority(args.mint_authority()); - mint.decimals = args.decimals(); + mint.set_mint_authority(&args.mint_authority); + mint.decimals = args.decimals; if let Some(freeze_authority) = args.freeze_authority() { mint.set_freeze_authority(freeze_authority); @@ -71,50 +71,39 @@ pub fn process_initialize_mint( } /// Instruction data for the `InitializeMint` instruction. -pub struct InitializeMint<'a> { - raw: *const u8, +#[repr(C)] +struct InitializeMint { + pub(crate) decimals: u8, - _data: PhantomData<&'a [u8]>, + pub(crate) mint_authority: Pubkey, + + freeze_authority: (u8, Pubkey), } -impl InitializeMint<'_> { +impl InitializeMint { #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result { + pub fn try_from_bytes(bytes: &[u8]) -> Result<&InitializeMint, ProgramError> { // The minimum expected size of the instruction data is either 34 or 66 bytes: // - decimals (1 byte) // - mint_authority (32 bytes) // - option + freeze_authority (1 byte + 32 bytes) - if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) { - return Err(ProgramError::InvalidInstructionData); + unsafe { + match bytes.len() { + 34 if *bytes.get_unchecked(33) == 0 => { + Ok(&*(bytes.as_ptr() as *const InitializeMint)) + } + 66 => Ok(&*(bytes.as_ptr() as *const InitializeMint)), + _ => Err(ProgramError::InvalidInstructionData), + } } - - Ok(InitializeMint { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn decimals(&self) -> u8 { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { *self.raw } - } - - #[inline] - pub fn mint_authority(&self) -> &Pubkey { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { &*(self.raw.add(1) as *const Pubkey) } } #[inline] pub fn freeze_authority(&self) -> Option<&Pubkey> { - // SAFETY: the `bytes` length was validated in `try_from_bytes`. - unsafe { - if *self.raw.add(33) == 0 { - Option::None - } else { - Option::Some(&*(self.raw.add(34) as *const Pubkey)) - } + if self.freeze_authority.0 == 0 { + Option::None + } else { + Option::Some(&self.freeze_authority.1) } } } diff --git a/p-token/src/processor/shared/transfer.rs b/p-token/src/processor/shared/transfer.rs index b98e7be6..2868678b 100644 --- a/p-token/src/processor/shared/transfer.rs +++ b/p-token/src/processor/shared/transfer.rs @@ -159,6 +159,8 @@ pub fn process_transfer( let destination_account = unsafe { load_mut_unchecked::(destination_account_info.borrow_mut_data_unchecked())? }; + // Note: The amount of a token account is always within the range of the + // mint supply (`u64`). destination_account.set_amount(destination_account.amount() + amount); if source_account.is_native() { From c12b54fe80faacdbd432e2374163f89d599c26eb Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 8 Mar 2025 02:38:20 +0000 Subject: [PATCH 7/7] Fix instruction data validation --- p-token/src/processor/set_authority.rs | 66 +++++++------------ .../src/processor/shared/initialize_mint.rs | 64 +++++++----------- 2 files changed, 44 insertions(+), 86 deletions(-) diff --git a/p-token/src/processor/set_authority.rs b/p-token/src/processor/set_authority.rs index da6f50ec..b621eeba 100644 --- a/p-token/src/processor/set_authority.rs +++ b/p-token/src/processor/set_authority.rs @@ -13,7 +13,22 @@ use super::validate_owner; pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { // Validates the instruction data. - let args = SetAuthority::try_from_bytes(instruction_data)?; + // SAFETY: The expected size of the instruction data is either 2 or 34 bytes: + // - authority_type (1 byte) + // - option + new_authority (1 byte + 32 bytes) + let (authority_type, new_authority) = unsafe { + match instruction_data.len() { + 2 if *instruction_data.get_unchecked(1) == 0 => ( + AuthorityType::try_from(*instruction_data.get_unchecked(0))?, + None, + ), + 34 if *instruction_data.get_unchecked(1) == 1 => ( + AuthorityType::try_from(*instruction_data.get_unchecked(0))?, + Some(&*(instruction_data.as_ptr().add(2) as *const Pubkey)), + ), + _ => return Err(ProgramError::InvalidInstructionData), + } + }; // Validates the accounts. @@ -30,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()); @@ -51,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(); @@ -66,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. @@ -74,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(); @@ -89,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(); @@ -105,40 +120,3 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) Ok(()) } - -#[repr(C)] -struct SetAuthority { - authority_type: u8, - - new_authority: (u8, Pubkey), -} - -impl SetAuthority { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result<&SetAuthority, ProgramError> { - // The minimum expected size of the instruction data is either 2 or 34 bytes: - // - authority_type (1 byte) - // - option + new_authority (1 byte + 32 bytes) - unsafe { - match bytes.len() { - 2 if *bytes.get_unchecked(1) == 0 => Ok(&*(bytes.as_ptr() as *const SetAuthority)), - 34 => Ok(&*(bytes.as_ptr() as *const SetAuthority)), - _ => Err(ProgramError::InvalidInstructionData), - } - } - } - - #[inline] - pub fn authority_type(&self) -> Result { - self.authority_type.try_into() - } - - #[inline] - pub fn new_authority(&self) -> Option<&Pubkey> { - if self.new_authority.0 == 0 { - Option::None - } else { - Option::Some(&self.new_authority.1) - } - } -} diff --git a/p-token/src/processor/shared/initialize_mint.rs b/p-token/src/processor/shared/initialize_mint.rs index 4804f0c8..657f5755 100644 --- a/p-token/src/processor/shared/initialize_mint.rs +++ b/p-token/src/processor/shared/initialize_mint.rs @@ -19,7 +19,25 @@ pub fn process_initialize_mint( ) -> ProgramResult { // Validates the instruction data. - let args = InitializeMint::try_from_bytes(instruction_data)?; + // SAFETY: The minimum size of the instruction data is either 34 or 66 bytes: + // - decimals (1 byte) + // - mint_authority (32 bytes) + // - option + freeze_authority (1 byte + 32 bytes) + let (decimals, mint_authority, freeze_authority) = unsafe { + match instruction_data.len() { + 34 if *instruction_data.get_unchecked(33) == 0 => ( + *instruction_data.get_unchecked(0), + &*(instruction_data.as_ptr().add(1) as *const Pubkey), + None, + ), + 66 if *instruction_data.get_unchecked(33) == 1 => ( + *instruction_data.get_unchecked(0), + &*(instruction_data.as_ptr().add(1) as *const Pubkey), + Some(&*(instruction_data.as_ptr().add(34) as *const Pubkey)), + ), + _ => return Err(ProgramError::InvalidInstructionData), + } + }; // Validates the accounts. @@ -60,50 +78,12 @@ pub fn process_initialize_mint( // Initialize the mint. mint.set_initialized(); - mint.set_mint_authority(&args.mint_authority); - mint.decimals = args.decimals; + mint.set_mint_authority(mint_authority); + mint.decimals = decimals; - if let Some(freeze_authority) = args.freeze_authority() { + if let Some(freeze_authority) = freeze_authority { mint.set_freeze_authority(freeze_authority); } Ok(()) } - -/// Instruction data for the `InitializeMint` instruction. -#[repr(C)] -struct InitializeMint { - pub(crate) decimals: u8, - - pub(crate) mint_authority: Pubkey, - - freeze_authority: (u8, Pubkey), -} - -impl InitializeMint { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result<&InitializeMint, ProgramError> { - // The minimum expected size of the instruction data is either 34 or 66 bytes: - // - decimals (1 byte) - // - mint_authority (32 bytes) - // - option + freeze_authority (1 byte + 32 bytes) - unsafe { - match bytes.len() { - 34 if *bytes.get_unchecked(33) == 0 => { - Ok(&*(bytes.as_ptr() as *const InitializeMint)) - } - 66 => Ok(&*(bytes.as_ptr() as *const InitializeMint)), - _ => Err(ProgramError::InvalidInstructionData), - } - } - } - - #[inline] - pub fn freeze_authority(&self) -> Option<&Pubkey> { - if self.freeze_authority.0 == 0 { - Option::None - } else { - Option::Some(&self.freeze_authority.1) - } - } -}