diff --git a/Cargo.lock b/Cargo.lock index a65a517821bbde..fb0f87b78b7640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6671,6 +6671,26 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-instruction" +version = "2.1.0" +dependencies = [ + "bincode", + "borsh 1.5.1", + "getrandom 0.2.10", + "js-sys", + "num-traits", + "rustc_version 0.4.1", + "serde", + "serde_derive", + "solana-define-syscall", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-instruction", + "solana-pubkey", + "wasm-bindgen", +] + [[package]] name = "solana-keygen" version = "2.1.0" @@ -7108,6 +7128,7 @@ dependencies = [ "solana-frozen-abi", "solana-frozen-abi-macro", "solana-hash", + "solana-instruction", "solana-logger", "solana-msg", "solana-program-memory", @@ -7191,6 +7212,7 @@ dependencies = [ "solana-compute-budget", "solana-feature-set", "solana-inline-spl", + "solana-instruction", "solana-log-collector", "solana-logger", "solana-program-runtime", diff --git a/Cargo.toml b/Cargo.toml index ed0b1b0504ee4c..2b8cbd137f4e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ members = [ "sdk/feature-set", "sdk/gen-headers", "sdk/hash", + "sdk/instruction", "sdk/macro", "sdk/msg", "sdk/package-metadata-macro", @@ -403,6 +404,7 @@ solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=2.1 solana-gossip = { path = "gossip", version = "=2.1.0" } solana-hash = { path = "sdk/hash", version = "=2.1.0" } solana-inline-spl = { path = "inline-spl", version = "=2.1.0" } +solana-instruction = { path = "sdk/instruction", version = "=2.1.0", default-features = false } solana-lattice-hash = { path = "lattice-hash", version = "=2.1.0" } solana-ledger = { path = "ledger", version = "=2.1.0" } solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.1.0" } diff --git a/frozen-abi/macro/src/lib.rs b/frozen-abi/macro/src/lib.rs index 9a735e2c5f7185..bb3f7886169111 100644 --- a/frozen-abi/macro/src/lib.rs +++ b/frozen-abi/macro/src/lib.rs @@ -256,7 +256,7 @@ fn do_derive_abi_enum_visitor(input: ItemEnum) -> TokenStream { let enum_name = #type_str; use ::serde::ser::Serialize; use ::solana_frozen_abi::abi_example::AbiExample; - digester.update_with_string(format!("enum {} (variants = {})", enum_name, #variant_count)); + digester.update_with_string(::std::format!("enum {} (variants = {})", enum_name, #variant_count)); #serialized_variants digester.create_child() } @@ -303,7 +303,7 @@ fn quote_for_test( ::solana_frozen_abi::__private::log::error!("digest error: {:#?}", result); } result.unwrap(); - let actual_digest = format!("{}", hash); + let actual_digest = ::std::format!("{}", hash); if ::std::env::var("SOLANA_ABI_BULK_UPDATE").is_ok() { if #expected_digest != actual_digest { #p!("sed -i -e 's/{}/{}/g' $(git grep --files-with-matches frozen_abi)", #expected_digest, hash); diff --git a/program-test/Cargo.toml b/program-test/Cargo.toml index c96cb8d28b4ceb..d3df42d38abe44 100644 --- a/program-test/Cargo.toml +++ b/program-test/Cargo.toml @@ -25,6 +25,7 @@ solana-bpf-loader-program = { workspace = true } solana-compute-budget = { workspace = true } solana-feature-set = { workspace = true } solana-inline-spl = { workspace = true } +solana-instruction = { workspace = true } solana-log-collector = { workspace = true } solana-logger = { workspace = true } solana-program-runtime = { workspace = true } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index d4d6388436c373..38199b270f1d52 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -17,6 +17,7 @@ use { solana_bpf_loader_program::serialization::serialize_parameters, solana_compute_budget::compute_budget::ComputeBudget, solana_feature_set::FEATURE_NAMES, + solana_instruction::{error::InstructionError, Instruction}, solana_log_collector::ic_msg, solana_program_runtime::{ invoke_context::BuiltinFunctionWithContext, loaded_programs::ProgramCacheEntry, stable_log, @@ -37,7 +38,6 @@ use { fee_calculator::{FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE}, genesis_config::{ClusterType, GenesisConfig}, hash::Hash, - instruction::{Instruction, InstructionError}, native_token::sol_to_lamports, poh_config::PohConfig, program_error::{ProgramError, UNSUPPORTED_SYSVAR}, diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 0a49b92d94e2d8..cbe090c6449fe3 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5241,6 +5241,22 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-instruction" +version = "2.1.0" +dependencies = [ + "bincode", + "borsh 1.5.1", + "getrandom 0.2.10", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-define-syscall", + "solana-pubkey", + "wasm-bindgen", +] + [[package]] name = "solana-lattice-hash" version = "2.1.0" @@ -5498,6 +5514,7 @@ dependencies = [ "solana-decode-error", "solana-define-syscall", "solana-hash", + "solana-instruction", "solana-msg", "solana-program-memory", "solana-program-option", @@ -5572,6 +5589,7 @@ dependencies = [ "solana-compute-budget", "solana-feature-set", "solana-inline-spl", + "solana-instruction", "solana-log-collector", "solana-logger", "solana-program-runtime", diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d4e78bf5180ae4..78121d7bcf453a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -247,7 +247,7 @@ struct RentMetrics { pub type BankStatusCache = StatusCache>; #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "EQwW6Ym6ECKaAREnAgkhXYisBQovuraBKSALdJ8koZzq") + frozen_abi(digest = "BswQL6n7kKwgHFKcwMCQcrWjt8h59Vh6KkNb75iaqG2B") )] pub type BankSlotDelta = SlotDelta>; diff --git a/sdk/instruction/Cargo.toml b/sdk/instruction/Cargo.toml new file mode 100644 index 00000000000000..4fd6a8d3c7947f --- /dev/null +++ b/sdk/instruction/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "solana-instruction" +description = "Types for directing the execution of Solana programs." +documentation = "https://docs.rs/solana-instruction" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bincode = { workspace = true, optional = true } +borsh = { workspace = true, optional = true } +num-traits = { workspace = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-frozen-abi = { workspace = true, optional = true } +solana-frozen-abi-macro = { workspace = true, optional = true } +solana-pubkey = { workspace = true, default-features = false } + +[target.'cfg(target_os = "solana")'.dependencies] +solana-define-syscall = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true, features = ["js", "wasm-bindgen"] } +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } + +[dev-dependencies] +solana-instruction = { path = ".", features = ["borsh"] } + +[build-dependencies] +rustc_version = { workspace = true, optional = true } + +[features] +bincode = ["dep:bincode", "dep:serde"] +borsh = ["dep:borsh"] +default = ["std"] +frozen-abi = [ + "dep:rustc_version", + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "serde", + "std", +] +serde = [ + "dep:serde", + "dep:serde_derive", + "solana-pubkey/serde", +] +std = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lints] +workspace = true diff --git a/sdk/instruction/build.rs b/sdk/instruction/build.rs new file mode 120000 index 00000000000000..84539eddaa6ded --- /dev/null +++ b/sdk/instruction/build.rs @@ -0,0 +1 @@ +../../frozen-abi/build.rs \ No newline at end of file diff --git a/sdk/instruction/src/account_meta.rs b/sdk/instruction/src/account_meta.rs new file mode 100644 index 00000000000000..7f9f9a3dbc2974 --- /dev/null +++ b/sdk/instruction/src/account_meta.rs @@ -0,0 +1,102 @@ +use solana_pubkey::Pubkey; + +/// Describes a single account read or written by a program during instruction +/// execution. +/// +/// When constructing an [`Instruction`], a list of all accounts that may be +/// read or written during the execution of that instruction must be supplied. +/// Any account that may be mutated by the program during execution, either its +/// data or metadata such as held lamports, must be writable. +/// +/// Note that because the Solana runtime schedules parallel transaction +/// execution around which accounts are writable, care should be taken that only +/// accounts which actually may be mutated are specified as writable. As the +/// default [`AccountMeta::new`] constructor creates writable accounts, this is +/// a minor hazard: use [`AccountMeta::new_readonly`] to specify that an account +/// is not writable. +#[repr(C)] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Serialize, serde_derive::Deserialize) +)] +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct AccountMeta { + /// An account's public key. + pub pubkey: Pubkey, + /// True if an `Instruction` requires a `Transaction` signature matching `pubkey`. + pub is_signer: bool, + /// True if the account data or metadata may be mutated during program execution. + pub is_writable: bool, +} + +impl AccountMeta { + /// Construct metadata for a writable account. + /// + /// # Examples + /// + /// ``` + /// # use solana_pubkey::Pubkey; + /// # use solana_instruction::{AccountMeta, Instruction}; + /// # use borsh::{BorshSerialize, BorshDeserialize}; + /// # + /// # #[derive(BorshSerialize, BorshDeserialize)] + /// # #[borsh(crate = "borsh")] + /// # pub struct MyInstruction; + /// # + /// # let instruction = MyInstruction; + /// # let from = Pubkey::new_unique(); + /// # let to = Pubkey::new_unique(); + /// # let program_id = Pubkey::new_unique(); + /// let instr = Instruction::new_with_borsh( + /// program_id, + /// &instruction, + /// vec![ + /// AccountMeta::new(from, true), + /// AccountMeta::new(to, false), + /// ], + /// ); + /// ``` + pub fn new(pubkey: Pubkey, is_signer: bool) -> Self { + Self { + pubkey, + is_signer, + is_writable: true, + } + } + + /// Construct metadata for a read-only account. + /// + /// # Examples + /// + /// ``` + /// # use solana_pubkey::Pubkey; + /// # use solana_instruction::{AccountMeta, Instruction}; + /// # use borsh::{BorshSerialize, BorshDeserialize}; + /// # + /// # #[derive(BorshSerialize, BorshDeserialize)] + /// # #[borsh(crate = "borsh")] + /// # pub struct MyInstruction; + /// # + /// # let instruction = MyInstruction; + /// # let from = Pubkey::new_unique(); + /// # let to = Pubkey::new_unique(); + /// # let from_account_storage = Pubkey::new_unique(); + /// # let program_id = Pubkey::new_unique(); + /// let instr = Instruction::new_with_borsh( + /// program_id, + /// &instruction, + /// vec![ + /// AccountMeta::new(from, true), + /// AccountMeta::new(to, false), + /// AccountMeta::new_readonly(from_account_storage, false), + /// ], + /// ); + /// ``` + pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self { + Self { + pubkey, + is_signer, + is_writable: false, + } + } +} diff --git a/sdk/instruction/src/error.rs b/sdk/instruction/src/error.rs new file mode 100644 index 00000000000000..5cef257b3f31cb --- /dev/null +++ b/sdk/instruction/src/error.rs @@ -0,0 +1,434 @@ +#[cfg(feature = "frozen-abi")] +use solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample}; +#[cfg(feature = "std")] +use { + core::fmt, + num_traits::ToPrimitive, + std::string::{String, ToString}, +}; + +/// Builtin return values occupy the upper 32 bits +const BUILTIN_BIT_SHIFT: usize = 32; +macro_rules! to_builtin { + ($error:expr) => { + ($error as u64) << BUILTIN_BIT_SHIFT + }; +} + +pub const CUSTOM_ZERO: u64 = to_builtin!(1); +pub const INVALID_ARGUMENT: u64 = to_builtin!(2); +pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3); +pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4); +pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5); +pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6); +pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7); +pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8); +pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9); +pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10); +pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11); +pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12); +pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13); +pub const INVALID_SEEDS: u64 = to_builtin!(14); +pub const BORSH_IO_ERROR: u64 = to_builtin!(15); +pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16); +pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17); +pub const ILLEGAL_OWNER: u64 = to_builtin!(18); +pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19); +pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20); +pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21); +pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(22); +pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(23); +pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(24); +pub const IMMUTABLE: u64 = to_builtin!(25); +pub const INCORRECT_AUTHORITY: u64 = to_builtin!(26); +// Warning: Any new error codes added here must also be: +// - Added to the below conversions +// - Added as an equivalent to ProgramError and InstructionError +// - Be featurized in the BPF loader to return `InstructionError::InvalidError` +// until the feature is activated + +/// Reasons the runtime might have rejected an instruction. +/// +/// Members of this enum must not be removed, but new ones can be added. +/// Also, it is crucial that meta-information if any that comes along with +/// an error be consistent across software versions. For example, it is +/// dangerous to include error strings from 3rd party crates because they could +/// change at any time and changes to them are difficult to detect. +#[cfg(feature = "std")] +#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Serialize, serde_derive::Deserialize) +)] +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum InstructionError { + /// Deprecated! Use CustomError instead! + /// The program instruction returned an error + GenericError, + + /// The arguments provided to a program were invalid + InvalidArgument, + + /// An instruction's data contents were invalid + InvalidInstructionData, + + /// An account's data contents was invalid + InvalidAccountData, + + /// An account's data was too small + AccountDataTooSmall, + + /// An account's balance was too small to complete the instruction + InsufficientFunds, + + /// The account did not have the expected program id + IncorrectProgramId, + + /// A signature was required but not found + MissingRequiredSignature, + + /// An initialize instruction was sent to an account that has already been initialized. + AccountAlreadyInitialized, + + /// An attempt to operate on an account that hasn't been initialized. + UninitializedAccount, + + /// Program's instruction lamport balance does not equal the balance after the instruction + UnbalancedInstruction, + + /// Program illegally modified an account's program id + ModifiedProgramId, + + /// Program spent the lamports of an account that doesn't belong to it + ExternalAccountLamportSpend, + + /// Program modified the data of an account that doesn't belong to it + ExternalAccountDataModified, + + /// Read-only account's lamports modified + ReadonlyLamportChange, + + /// Read-only account's data was modified + ReadonlyDataModified, + + /// An account was referenced more than once in a single instruction + // Deprecated, instructions can now contain duplicate accounts + DuplicateAccountIndex, + + /// Executable bit on account changed, but shouldn't have + ExecutableModified, + + /// Rent_epoch account changed, but shouldn't have + RentEpochModified, + + /// The instruction expected additional account keys + NotEnoughAccountKeys, + + /// Program other than the account's owner changed the size of the account data + AccountDataSizeChanged, + + /// The instruction expected an executable account + AccountNotExecutable, + + /// Failed to borrow a reference to account data, already borrowed + AccountBorrowFailed, + + /// Account data has an outstanding reference after a program's execution + AccountBorrowOutstanding, + + /// The same account was multiply passed to an on-chain program's entrypoint, but the program + /// modified them differently. A program can only modify one instance of the account because + /// the runtime cannot determine which changes to pick or how to merge them if both are modified + DuplicateAccountOutOfSync, + + /// Allows on-chain programs to implement program-specific error types and see them returned + /// by the Solana runtime. A program-specific error may be any type that is represented as + /// or serialized to a u32 integer. + Custom(u32), + + /// The return value from the program was invalid. Valid errors are either a defined builtin + /// error value or a user-defined error in the lower 32 bits. + InvalidError, + + /// Executable account's data was modified + ExecutableDataModified, + + /// Executable account's lamports modified + ExecutableLamportChange, + + /// Executable accounts must be rent exempt + ExecutableAccountNotRentExempt, + + /// Unsupported program id + UnsupportedProgramId, + + /// Cross-program invocation call depth too deep + CallDepth, + + /// An account required by the instruction is missing + MissingAccount, + + /// Cross-program invocation reentrancy not allowed for this instruction + ReentrancyNotAllowed, + + /// Length of the seed is too long for address generation + MaxSeedLengthExceeded, + + /// Provided seeds do not result in a valid address + InvalidSeeds, + + /// Failed to reallocate account data of this length + InvalidRealloc, + + /// Computational budget exceeded + ComputationalBudgetExceeded, + + /// Cross-program invocation with unauthorized signer or writable account + PrivilegeEscalation, + + /// Failed to create program execution environment + ProgramEnvironmentSetupFailure, + + /// Program failed to complete + ProgramFailedToComplete, + + /// Program failed to compile + ProgramFailedToCompile, + + /// Account is immutable + Immutable, + + /// Incorrect authority provided + IncorrectAuthority, + + /// Failed to serialize or deserialize account data + /// + /// Warning: This error should never be emitted by the runtime. + /// + /// This error includes strings from the underlying 3rd party Borsh crate + /// which can be dangerous because the error strings could change across + /// Borsh versions. Only programs can use this error because they are + /// consistent across Solana software versions. + /// + BorshIoError(String), + + /// An account does not have enough lamports to be rent-exempt + AccountNotRentExempt, + + /// Invalid account owner + InvalidAccountOwner, + + /// Program arithmetic overflowed + ArithmeticOverflow, + + /// Unsupported sysvar + UnsupportedSysvar, + + /// Illegal account owner + IllegalOwner, + + /// Accounts data allocations exceeded the maximum allowed per transaction + MaxAccountsDataAllocationsExceeded, + + /// Max accounts exceeded + MaxAccountsExceeded, + + /// Max instruction trace length exceeded + MaxInstructionTraceLengthExceeded, + + /// Builtin programs must consume compute units + BuiltinProgramsMustConsumeComputeUnits, + // Note: For any new error added here an equivalent ProgramError and its + // conversions must also be added +} + +#[cfg(feature = "std")] +impl std::error::Error for InstructionError {} + +#[cfg(feature = "std")] +impl fmt::Display for InstructionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InstructionError::GenericError => f.write_str("generic instruction error"), + InstructionError::InvalidArgument => f.write_str("invalid program argument"), + InstructionError::InvalidInstructionData => f.write_str("invalid instruction data"), + InstructionError::InvalidAccountData => { + f.write_str("invalid account data for instruction") + } + InstructionError::AccountDataTooSmall => { + f.write_str("account data too small for instruction") + } + InstructionError::InsufficientFunds => { + f.write_str("insufficient funds for instruction") + } + InstructionError::IncorrectProgramId => { + f.write_str("incorrect program id for instruction") + } + InstructionError::MissingRequiredSignature => { + f.write_str("missing required signature for instruction") + } + InstructionError::AccountAlreadyInitialized => { + f.write_str("instruction requires an uninitialized account") + } + InstructionError::UninitializedAccount => { + f.write_str("instruction requires an initialized account") + } + InstructionError::UnbalancedInstruction => { + f.write_str("sum of account balances before and after instruction do not match") + } + InstructionError::ModifiedProgramId => { + f.write_str("instruction illegally modified the program id of an account") + } + InstructionError::ExternalAccountLamportSpend => { + f.write_str("instruction spent from the balance of an account it does not own") + } + InstructionError::ExternalAccountDataModified => { + f.write_str("instruction modified data of an account it does not own") + } + InstructionError::ReadonlyLamportChange => { + f.write_str("instruction changed the balance of a read-only account") + } + InstructionError::ReadonlyDataModified => { + f.write_str("instruction modified data of a read-only account") + } + InstructionError::DuplicateAccountIndex => { + f.write_str("instruction contains duplicate accounts") + } + InstructionError::ExecutableModified => { + f.write_str("instruction changed executable bit of an account") + } + InstructionError::RentEpochModified => { + f.write_str("instruction modified rent epoch of an account") + } + InstructionError::NotEnoughAccountKeys => { + f.write_str("insufficient account keys for instruction") + } + InstructionError::AccountDataSizeChanged => f.write_str( + "program other than the account's owner changed the size of the account data", + ), + InstructionError::AccountNotExecutable => { + f.write_str("instruction expected an executable account") + } + InstructionError::AccountBorrowFailed => f.write_str( + "instruction tries to borrow reference for an account which is already borrowed", + ), + InstructionError::AccountBorrowOutstanding => { + f.write_str("instruction left account with an outstanding borrowed reference") + } + InstructionError::DuplicateAccountOutOfSync => { + f.write_str("instruction modifications of multiply-passed account differ") + } + InstructionError::Custom(num) => { + write!(f, "custom program error: {num:#x}") + } + InstructionError::InvalidError => f.write_str("program returned invalid error code"), + InstructionError::ExecutableDataModified => { + f.write_str("instruction changed executable accounts data") + } + InstructionError::ExecutableLamportChange => { + f.write_str("instruction changed the balance of an executable account") + } + InstructionError::ExecutableAccountNotRentExempt => { + f.write_str("executable accounts must be rent exempt") + } + InstructionError::UnsupportedProgramId => f.write_str("Unsupported program id"), + InstructionError::CallDepth => { + f.write_str("Cross-program invocation call depth too deep") + } + InstructionError::MissingAccount => { + f.write_str("An account required by the instruction is missing") + } + InstructionError::ReentrancyNotAllowed => { + f.write_str("Cross-program invocation reentrancy not allowed for this instruction") + } + InstructionError::MaxSeedLengthExceeded => { + f.write_str("Length of the seed is too long for address generation") + } + InstructionError::InvalidSeeds => { + f.write_str("Provided seeds do not result in a valid address") + } + InstructionError::InvalidRealloc => f.write_str("Failed to reallocate account data"), + InstructionError::ComputationalBudgetExceeded => { + f.write_str("Computational budget exceeded") + } + InstructionError::PrivilegeEscalation => { + f.write_str("Cross-program invocation with unauthorized signer or writable account") + } + InstructionError::ProgramEnvironmentSetupFailure => { + f.write_str("Failed to create program execution environment") + } + InstructionError::ProgramFailedToComplete => f.write_str("Program failed to complete"), + InstructionError::ProgramFailedToCompile => f.write_str("Program failed to compile"), + InstructionError::Immutable => f.write_str("Account is immutable"), + InstructionError::IncorrectAuthority => f.write_str("Incorrect authority provided"), + InstructionError::BorshIoError(s) => { + write!(f, "Failed to serialize or deserialize account data: {s}",) + } + InstructionError::AccountNotRentExempt => { + f.write_str("An account does not have enough lamports to be rent-exempt") + } + InstructionError::InvalidAccountOwner => f.write_str("Invalid account owner"), + InstructionError::ArithmeticOverflow => f.write_str("Program arithmetic overflowed"), + InstructionError::UnsupportedSysvar => f.write_str("Unsupported sysvar"), + InstructionError::IllegalOwner => f.write_str("Provided owner is not allowed"), + InstructionError::MaxAccountsDataAllocationsExceeded => f.write_str( + "Accounts data allocations exceeded the maximum allowed per transaction", + ), + InstructionError::MaxAccountsExceeded => f.write_str("Max accounts exceeded"), + InstructionError::MaxInstructionTraceLengthExceeded => { + f.write_str("Max instruction trace length exceeded") + } + InstructionError::BuiltinProgramsMustConsumeComputeUnits => { + f.write_str("Builtin programs must consume compute units") + } + } + } +} + +#[cfg(feature = "std")] +impl From for InstructionError +where + T: ToPrimitive, +{ + fn from(error: T) -> Self { + let error = error.to_u64().unwrap_or(0xbad_c0de); + match error { + CUSTOM_ZERO => Self::Custom(0), + INVALID_ARGUMENT => Self::InvalidArgument, + INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData, + INVALID_ACCOUNT_DATA => Self::InvalidAccountData, + ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall, + INSUFFICIENT_FUNDS => Self::InsufficientFunds, + INCORRECT_PROGRAM_ID => Self::IncorrectProgramId, + MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature, + ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized, + UNINITIALIZED_ACCOUNT => Self::UninitializedAccount, + NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys, + ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed, + MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded, + INVALID_SEEDS => Self::InvalidSeeds, + BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()), + ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt, + UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar, + ILLEGAL_OWNER => Self::IllegalOwner, + MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded, + INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc, + MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded, + BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => { + Self::BuiltinProgramsMustConsumeComputeUnits + } + INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner, + ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow, + IMMUTABLE => Self::Immutable, + INCORRECT_AUTHORITY => Self::IncorrectAuthority, + _ => { + // A valid custom error has no bits set in the upper 32 + if error >> BUILTIN_BIT_SHIFT == 0 { + Self::Custom(error as u32) + } else { + Self::InvalidError + } + } + } + } +} diff --git a/sdk/instruction/src/lib.rs b/sdk/instruction/src/lib.rs new file mode 100644 index 00000000000000..1b3aa31855ad98 --- /dev/null +++ b/sdk/instruction/src/lib.rs @@ -0,0 +1,292 @@ +//! Types for directing the execution of Solana programs. +//! +//! Every invocation of a Solana program executes a single instruction, as +//! defined by the [`Instruction`] type. An instruction is primarily a vector of +//! bytes, the contents of which are program-specific, and not interpreted by +//! the Solana runtime. This allows flexibility in how programs behave, how they +//! are controlled by client software, and what data encodings they use. +//! +//! Besides the instruction data, every account a program may read or write +//! while executing a given instruction is also included in `Instruction`, as +//! [`AccountMeta`] values. The runtime uses this information to efficiently +//! schedule execution of transactions. +#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] +#![allow(clippy::arithmetic_side_effects)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; +#[cfg(feature = "std")] +use {solana_pubkey::Pubkey, std::vec::Vec}; +pub mod account_meta; +#[cfg(feature = "std")] +pub use account_meta::AccountMeta; +pub mod error; +#[cfg(target_os = "solana")] +pub mod syscalls; + +/// A directive for a single invocation of a Solana program. +/// +/// An instruction specifies which program it is calling, which accounts it may +/// read or modify, and additional data that serves as input to the program. One +/// or more instructions are included in transactions submitted by Solana +/// clients. Instructions are also used to describe [cross-program +/// invocations][cpi]. +/// +/// [cpi]: https://solana.com/docs/core/cpi +/// +/// During execution, a program will receive a list of account data as one of +/// its arguments, in the same order as specified during `Instruction` +/// construction. +/// +/// While Solana is agnostic to the format of the instruction data, it has +/// built-in support for serialization via [`borsh`] and [`bincode`]. +/// +/// [`borsh`]: https://docs.rs/borsh/latest/borsh/ +/// [`bincode`]: https://docs.rs/bincode/latest/bincode/ +/// +/// # Specifying account metadata +/// +/// When constructing an [`Instruction`], a list of all accounts that may be +/// read or written during the execution of that instruction must be supplied as +/// [`AccountMeta`] values. +/// +/// Any account whose data may be mutated by the program during execution must +/// be specified as writable. During execution, writing to an account that was +/// not specified as writable will cause the transaction to fail. Writing to an +/// account that is not owned by the program will cause the transaction to fail. +/// +/// Any account whose lamport balance may be mutated by the program during +/// execution must be specified as writable. During execution, mutating the +/// lamports of an account that was not specified as writable will cause the +/// transaction to fail. While _subtracting_ lamports from an account not owned +/// by the program will cause the transaction to fail, _adding_ lamports to any +/// account is allowed, as long is it is mutable. +/// +/// Accounts that are not read or written by the program may still be specified +/// in an `Instruction`'s account list. These will affect scheduling of program +/// execution by the runtime, but will otherwise be ignored. +/// +/// When building a transaction, the Solana runtime coalesces all accounts used +/// by all instructions in that transaction, along with accounts and permissions +/// required by the runtime, into a single account list. Some accounts and +/// account permissions required by the runtime to process a transaction are +/// _not_ required to be included in an `Instruction`s account list. These +/// include: +/// +/// - The program ID — it is a separate field of `Instruction` +/// - The transaction's fee-paying account — it is added during [`Message`] +/// construction. A program may still require the fee payer as part of the +/// account list if it directly references it. +/// +/// [`Message`]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html +/// +/// Programs may require signatures from some accounts, in which case they +/// should be specified as signers during `Instruction` construction. The +/// program must still validate during execution that the account is a signer. +#[cfg(all(feature = "std", not(target_arch = "wasm32")))] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Serialize, serde_derive::Deserialize) +)] +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Instruction { + /// Pubkey of the program that executes this instruction. + pub program_id: Pubkey, + /// Metadata describing accounts that should be passed to the program. + pub accounts: Vec, + /// Opaque data passed to the program for its own interpretation. + pub data: Vec, +} + +/// wasm-bindgen version of the Instruction struct. +/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671 +/// is fixed. This must not diverge from the regular non-wasm Instruction struct. +#[cfg(all(feature = "std", target_arch = "wasm32"))] +#[wasm_bindgen::prelude::wasm_bindgen] +pub struct Instruction { + #[wasm_bindgen(skip)] + pub program_id: Pubkey, + #[wasm_bindgen(skip)] + pub accounts: Vec, + #[wasm_bindgen(skip)] + pub data: Vec, +} + +#[cfg(feature = "std")] +impl Instruction { + #[cfg(feature = "borsh")] + /// Create a new instruction from a value, encoded with [`borsh`]. + /// + /// [`borsh`]: https://docs.rs/borsh/latest/borsh/ + /// + /// `program_id` is the address of the program that will execute the instruction. + /// `accounts` contains a description of all accounts that may be accessed by the program. + /// + /// Borsh serialization is often preferred over bincode as it has a stable + /// [specification] and an [implementation in JavaScript][jsb], neither of + /// which are true of bincode. + /// + /// [specification]: https://borsh.io/ + /// [jsb]: https://github.com/near/borsh-js + /// + /// # Examples + /// + /// ``` + /// # use solana_pubkey::Pubkey; + /// # use solana_instruction::{AccountMeta, Instruction}; + /// # use borsh::{BorshSerialize, BorshDeserialize}; + /// # + /// #[derive(BorshSerialize, BorshDeserialize)] + /// # #[borsh(crate = "borsh")] + /// pub struct MyInstruction { + /// pub lamports: u64, + /// } + /// + /// pub fn create_instruction( + /// program_id: &Pubkey, + /// from: &Pubkey, + /// to: &Pubkey, + /// lamports: u64, + /// ) -> Instruction { + /// let instr = MyInstruction { lamports }; + /// + /// Instruction::new_with_borsh( + /// *program_id, + /// &instr, + /// vec![ + /// AccountMeta::new(*from, true), + /// AccountMeta::new(*to, false), + /// ], + /// ) + /// } + /// ``` + pub fn new_with_borsh( + program_id: Pubkey, + data: &T, + accounts: Vec, + ) -> Self { + let data = borsh::to_vec(data).unwrap(); + Self { + program_id, + accounts, + data, + } + } + + #[cfg(feature = "bincode")] + /// Create a new instruction from a value, encoded with [`bincode`]. + /// + /// [`bincode`]: https://docs.rs/bincode/latest/bincode/ + /// + /// `program_id` is the address of the program that will execute the instruction. + /// `accounts` contains a description of all accounts that may be accessed by the program. + /// + /// # Examples + /// + /// ``` + /// # use solana_pubkey::Pubkey; + /// # use solana_instruction::{AccountMeta, Instruction}; + /// # use serde::{Serialize, Deserialize}; + /// # + /// #[derive(Serialize, Deserialize)] + /// pub struct MyInstruction { + /// pub lamports: u64, + /// } + /// + /// pub fn create_instruction( + /// program_id: &Pubkey, + /// from: &Pubkey, + /// to: &Pubkey, + /// lamports: u64, + /// ) -> Instruction { + /// let instr = MyInstruction { lamports }; + /// + /// Instruction::new_with_bincode( + /// *program_id, + /// &instr, + /// vec![ + /// AccountMeta::new(*from, true), + /// AccountMeta::new(*to, false), + /// ], + /// ) + /// } + /// ``` + pub fn new_with_bincode( + program_id: Pubkey, + data: &T, + accounts: Vec, + ) -> Self { + let data = bincode::serialize(data).unwrap(); + Self { + program_id, + accounts, + data, + } + } + + /// Create a new instruction from a byte slice. + /// + /// `program_id` is the address of the program that will execute the instruction. + /// `accounts` contains a description of all accounts that may be accessed by the program. + /// + /// The caller is responsible for ensuring the correct encoding of `data` as expected + /// by the callee program. + /// + /// # Examples + /// + /// ``` + /// # use solana_pubkey::Pubkey; + /// # use solana_instruction::{AccountMeta, Instruction}; + /// # + /// # use borsh::{io::Error, BorshSerialize, BorshDeserialize}; + /// # + /// #[derive(BorshSerialize, BorshDeserialize)] + /// # #[borsh(crate = "borsh")] + /// pub struct MyInstruction { + /// pub lamports: u64, + /// } + /// + /// pub fn create_instruction( + /// program_id: &Pubkey, + /// from: &Pubkey, + /// to: &Pubkey, + /// lamports: u64, + /// ) -> Result { + /// let instr = MyInstruction { lamports }; + /// + /// let mut instr_in_bytes: Vec = Vec::new(); + /// instr.serialize(&mut instr_in_bytes)?; + /// + /// Ok(Instruction::new_with_bytes( + /// *program_id, + /// &instr_in_bytes, + /// vec![ + /// AccountMeta::new(*from, true), + /// AccountMeta::new(*to, false), + /// ], + /// )) + /// } + /// ``` + pub fn new_with_bytes(program_id: Pubkey, data: &[u8], accounts: Vec) -> Self { + Self { + program_id, + accounts, + data: data.to_vec(), + } + } +} + +// Stack height when processing transaction-level instructions +pub const TRANSACTION_LEVEL_STACK_HEIGHT: usize = 1; + +/// Use to query and convey information about the sibling instruction components +/// when calling the `sol_get_processed_sibling_instruction` syscall. +#[repr(C)] +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +pub struct ProcessedSiblingInstruction { + /// Length of the instruction data + pub data_len: u64, + /// Number of AccountMeta structures + pub accounts_len: u64, +} diff --git a/sdk/instruction/src/syscalls.rs b/sdk/instruction/src/syscalls.rs new file mode 100644 index 00000000000000..854fea7de5f190 --- /dev/null +++ b/sdk/instruction/src/syscalls.rs @@ -0,0 +1,8 @@ +use { + crate::{AccountMeta, ProcessedSiblingInstruction}, + solana_define_syscall::define_syscall, + solana_pubkey::Pubkey, +}; + +define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut ProcessedSiblingInstruction, program_id: *mut Pubkey, data: *mut u8, accounts: *mut AccountMeta) -> u64); +define_syscall!(fn sol_get_stack_height() -> u64); diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 1a25c1fde1352d..2f2c6feaebad69 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -41,6 +41,11 @@ solana-hash = { workspace = true, features = [ "serde", "std", ] } +solana-instruction = { workspace = true, default-features = false, features = [ + "bincode", + "serde", + "std", +] } solana-msg = { workspace = true } solana-program-memory = { workspace = true } solana-program-option = { workspace = true } @@ -107,13 +112,20 @@ crate-type = ["cdylib", "rlib"] [features] default = ["borsh"] -borsh = ["dep:borsh", "dep:borsh0-10", "solana-hash/borsh", "solana-pubkey/borsh"] +borsh = [ + "dep:borsh", + "dep:borsh0-10", + "solana-hash/borsh", + "solana-instruction/borsh", + "solana-pubkey/borsh" +] dev-context-only-utils = ["dep:qualifier_attr"] frozen-abi = [ "dep:rustc_version", "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", "solana-hash/frozen-abi", + "solana-instruction/frozen-abi", "solana-pubkey/frozen-abi", "solana-short-vec/frozen-abi" ] diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 2a686c75dec2e6..c380ecdbcde9c3 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -1,631 +1,14 @@ -//! Types for directing the execution of Solana programs. -//! -//! Every invocation of a Solana program executes a single instruction, as -//! defined by the [`Instruction`] type. An instruction is primarily a vector of -//! bytes, the contents of which are program-specific, and not interpreted by -//! the Solana runtime. This allows flexibility in how programs behave, how they -//! are controlled by client software, and what data encodings they use. -//! -//! Besides the instruction data, every account a program may read or write -//! while executing a given instruction is also included in `Instruction`, as -//! [`AccountMeta`] values. The runtime uses this information to efficiently -//! schedule execution of transactions. - -#![allow(clippy::arithmetic_side_effects)] - -#[cfg(target_arch = "wasm32")] -use crate::wasm_bindgen; -#[cfg(feature = "borsh")] -use borsh::BorshSerialize; +#[cfg(feature = "frozen-abi")] +use solana_frozen_abi_macro::AbiExample; +pub use solana_instruction::{ + error::InstructionError, AccountMeta, Instruction, ProcessedSiblingInstruction, + TRANSACTION_LEVEL_STACK_HEIGHT, +}; use { - crate::pubkey::Pubkey, bincode::serialize, serde::Serialize, solana_sanitize::Sanitize, - solana_short_vec as short_vec, thiserror::Error, + bincode::serialize, serde::Serialize, solana_pubkey::Pubkey, solana_sanitize::Sanitize, + solana_short_vec as short_vec, }; -/// Reasons the runtime might have rejected an instruction. -/// -/// Members of this enum must not be removed, but new ones can be added. -/// Also, it is crucial that meta-information if any that comes along with -/// an error be consistent across software versions. For example, it is -/// dangerous to include error strings from 3rd party crates because they could -/// change at any time and changes to them are difficult to detect. -#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))] -#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone)] -pub enum InstructionError { - /// Deprecated! Use CustomError instead! - /// The program instruction returned an error - #[error("generic instruction error")] - GenericError, - - /// The arguments provided to a program were invalid - #[error("invalid program argument")] - InvalidArgument, - - /// An instruction's data contents were invalid - #[error("invalid instruction data")] - InvalidInstructionData, - - /// An account's data contents was invalid - #[error("invalid account data for instruction")] - InvalidAccountData, - - /// An account's data was too small - #[error("account data too small for instruction")] - AccountDataTooSmall, - - /// An account's balance was too small to complete the instruction - #[error("insufficient funds for instruction")] - InsufficientFunds, - - /// The account did not have the expected program id - #[error("incorrect program id for instruction")] - IncorrectProgramId, - - /// A signature was required but not found - #[error("missing required signature for instruction")] - MissingRequiredSignature, - - /// An initialize instruction was sent to an account that has already been initialized. - #[error("instruction requires an uninitialized account")] - AccountAlreadyInitialized, - - /// An attempt to operate on an account that hasn't been initialized. - #[error("instruction requires an initialized account")] - UninitializedAccount, - - /// Program's instruction lamport balance does not equal the balance after the instruction - #[error("sum of account balances before and after instruction do not match")] - UnbalancedInstruction, - - /// Program illegally modified an account's program id - #[error("instruction illegally modified the program id of an account")] - ModifiedProgramId, - - /// Program spent the lamports of an account that doesn't belong to it - #[error("instruction spent from the balance of an account it does not own")] - ExternalAccountLamportSpend, - - /// Program modified the data of an account that doesn't belong to it - #[error("instruction modified data of an account it does not own")] - ExternalAccountDataModified, - - /// Read-only account's lamports modified - #[error("instruction changed the balance of a read-only account")] - ReadonlyLamportChange, - - /// Read-only account's data was modified - #[error("instruction modified data of a read-only account")] - ReadonlyDataModified, - - /// An account was referenced more than once in a single instruction - // Deprecated, instructions can now contain duplicate accounts - #[error("instruction contains duplicate accounts")] - DuplicateAccountIndex, - - /// Executable bit on account changed, but shouldn't have - #[error("instruction changed executable bit of an account")] - ExecutableModified, - - /// Rent_epoch account changed, but shouldn't have - #[error("instruction modified rent epoch of an account")] - RentEpochModified, - - /// The instruction expected additional account keys - #[error("insufficient account keys for instruction")] - NotEnoughAccountKeys, - - /// Program other than the account's owner changed the size of the account data - #[error("program other than the account's owner changed the size of the account data")] - AccountDataSizeChanged, - - /// The instruction expected an executable account - #[error("instruction expected an executable account")] - AccountNotExecutable, - - /// Failed to borrow a reference to account data, already borrowed - #[error("instruction tries to borrow reference for an account which is already borrowed")] - AccountBorrowFailed, - - /// Account data has an outstanding reference after a program's execution - #[error("instruction left account with an outstanding borrowed reference")] - AccountBorrowOutstanding, - - /// The same account was multiply passed to an on-chain program's entrypoint, but the program - /// modified them differently. A program can only modify one instance of the account because - /// the runtime cannot determine which changes to pick or how to merge them if both are modified - #[error("instruction modifications of multiply-passed account differ")] - DuplicateAccountOutOfSync, - - /// Allows on-chain programs to implement program-specific error types and see them returned - /// by the Solana runtime. A program-specific error may be any type that is represented as - /// or serialized to a u32 integer. - #[error("custom program error: {0:#x}")] - Custom(u32), - - /// The return value from the program was invalid. Valid errors are either a defined builtin - /// error value or a user-defined error in the lower 32 bits. - #[error("program returned invalid error code")] - InvalidError, - - /// Executable account's data was modified - #[error("instruction changed executable accounts data")] - ExecutableDataModified, - - /// Executable account's lamports modified - #[error("instruction changed the balance of an executable account")] - ExecutableLamportChange, - - /// Executable accounts must be rent exempt - #[error("executable accounts must be rent exempt")] - ExecutableAccountNotRentExempt, - - /// Unsupported program id - #[error("Unsupported program id")] - UnsupportedProgramId, - - /// Cross-program invocation call depth too deep - #[error("Cross-program invocation call depth too deep")] - CallDepth, - - /// An account required by the instruction is missing - #[error("An account required by the instruction is missing")] - MissingAccount, - - /// Cross-program invocation reentrancy not allowed for this instruction - #[error("Cross-program invocation reentrancy not allowed for this instruction")] - ReentrancyNotAllowed, - - /// Length of the seed is too long for address generation - #[error("Length of the seed is too long for address generation")] - MaxSeedLengthExceeded, - - /// Provided seeds do not result in a valid address - #[error("Provided seeds do not result in a valid address")] - InvalidSeeds, - - /// Failed to reallocate account data of this length - #[error("Failed to reallocate account data")] - InvalidRealloc, - - /// Computational budget exceeded - #[error("Computational budget exceeded")] - ComputationalBudgetExceeded, - - /// Cross-program invocation with unauthorized signer or writable account - #[error("Cross-program invocation with unauthorized signer or writable account")] - PrivilegeEscalation, - - /// Failed to create program execution environment - #[error("Failed to create program execution environment")] - ProgramEnvironmentSetupFailure, - - /// Program failed to complete - #[error("Program failed to complete")] - ProgramFailedToComplete, - - /// Program failed to compile - #[error("Program failed to compile")] - ProgramFailedToCompile, - - /// Account is immutable - #[error("Account is immutable")] - Immutable, - - /// Incorrect authority provided - #[error("Incorrect authority provided")] - IncorrectAuthority, - - /// Failed to serialize or deserialize account data - /// - /// Warning: This error should never be emitted by the runtime. - /// - /// This error includes strings from the underlying 3rd party Borsh crate - /// which can be dangerous because the error strings could change across - /// Borsh versions. Only programs can use this error because they are - /// consistent across Solana software versions. - /// - #[error("Failed to serialize or deserialize account data: {0}")] - BorshIoError(String), - - /// An account does not have enough lamports to be rent-exempt - #[error("An account does not have enough lamports to be rent-exempt")] - AccountNotRentExempt, - - /// Invalid account owner - #[error("Invalid account owner")] - InvalidAccountOwner, - - /// Program arithmetic overflowed - #[error("Program arithmetic overflowed")] - ArithmeticOverflow, - - /// Unsupported sysvar - #[error("Unsupported sysvar")] - UnsupportedSysvar, - - /// Illegal account owner - #[error("Provided owner is not allowed")] - IllegalOwner, - - /// Accounts data allocations exceeded the maximum allowed per transaction - #[error("Accounts data allocations exceeded the maximum allowed per transaction")] - MaxAccountsDataAllocationsExceeded, - - /// Max accounts exceeded - #[error("Max accounts exceeded")] - MaxAccountsExceeded, - - /// Max instruction trace length exceeded - #[error("Max instruction trace length exceeded")] - MaxInstructionTraceLengthExceeded, - - /// Builtin programs must consume compute units - #[error("Builtin programs must consume compute units")] - BuiltinProgramsMustConsumeComputeUnits, - // Note: For any new error added here an equivalent ProgramError and its - // conversions must also be added -} - -/// A directive for a single invocation of a Solana program. -/// -/// An instruction specifies which program it is calling, which accounts it may -/// read or modify, and additional data that serves as input to the program. One -/// or more instructions are included in transactions submitted by Solana -/// clients. Instructions are also used to describe [cross-program -/// invocations][cpi]. -/// -/// [cpi]: https://solana.com/docs/core/cpi -/// -/// During execution, a program will receive a list of account data as one of -/// its arguments, in the same order as specified during `Instruction` -/// construction. -/// -/// While Solana is agnostic to the format of the instruction data, it has -/// built-in support for serialization via [`borsh`] and [`bincode`]. -/// -/// [`borsh`]: https://docs.rs/borsh/latest/borsh/ -/// [`bincode`]: https://docs.rs/bincode/latest/bincode/ -/// -/// # Specifying account metadata -/// -/// When constructing an [`Instruction`], a list of all accounts that may be -/// read or written during the execution of that instruction must be supplied as -/// [`AccountMeta`] values. -/// -/// Any account whose data may be mutated by the program during execution must -/// be specified as writable. During execution, writing to an account that was -/// not specified as writable will cause the transaction to fail. Writing to an -/// account that is not owned by the program will cause the transaction to fail. -/// -/// Any account whose lamport balance may be mutated by the program during -/// execution must be specified as writable. During execution, mutating the -/// lamports of an account that was not specified as writable will cause the -/// transaction to fail. While _subtracting_ lamports from an account not owned -/// by the program will cause the transaction to fail, _adding_ lamports to any -/// account is allowed, as long is it is mutable. -/// -/// Accounts that are not read or written by the program may still be specified -/// in an `Instruction`'s account list. These will affect scheduling of program -/// execution by the runtime, but will otherwise be ignored. -/// -/// When building a transaction, the Solana runtime coalesces all accounts used -/// by all instructions in that transaction, along with accounts and permissions -/// required by the runtime, into a single account list. Some accounts and -/// account permissions required by the runtime to process a transaction are -/// _not_ required to be included in an `Instruction`s account list. These -/// include: -/// -/// - The program ID — it is a separate field of `Instruction` -/// - The transaction's fee-paying account — it is added during [`Message`] -/// construction. A program may still require the fee payer as part of the -/// account list if it directly references it. -/// -/// [`Message`]: crate::message::Message -/// -/// Programs may require signatures from some accounts, in which case they -/// should be specified as signers during `Instruction` construction. The -/// program must still validate during execution that the account is a signer. -#[cfg(not(target_arch = "wasm32"))] -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Instruction { - /// Pubkey of the program that executes this instruction. - pub program_id: Pubkey, - /// Metadata describing accounts that should be passed to the program. - pub accounts: Vec, - /// Opaque data passed to the program for its own interpretation. - pub data: Vec, -} - -/// wasm-bindgen version of the Instruction struct. -/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671 -/// is fixed. This must not diverge from the regular non-wasm Instruction struct. -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct Instruction { - #[wasm_bindgen(skip)] - pub program_id: Pubkey, - #[wasm_bindgen(skip)] - pub accounts: Vec, - #[wasm_bindgen(skip)] - pub data: Vec, -} - -impl Instruction { - #[cfg(feature = "borsh")] - /// Create a new instruction from a value, encoded with [`borsh`]. - /// - /// [`borsh`]: https://docs.rs/borsh/latest/borsh/ - /// - /// `program_id` is the address of the program that will execute the instruction. - /// `accounts` contains a description of all accounts that may be accessed by the program. - /// - /// Borsh serialization is often preferred over bincode as it has a stable - /// [specification] and an [implementation in JavaScript][jsb], neither of - /// which are true of bincode. - /// - /// [specification]: https://borsh.io/ - /// [jsb]: https://github.com/near/borsh-js - /// - /// # Examples - /// - /// ``` - /// # use solana_program::{ - /// # pubkey::Pubkey, - /// # instruction::{AccountMeta, Instruction}, - /// # }; - /// # use borsh::{BorshSerialize, BorshDeserialize}; - /// # - /// #[derive(BorshSerialize, BorshDeserialize)] - /// # #[borsh(crate = "borsh")] - /// pub struct MyInstruction { - /// pub lamports: u64, - /// } - /// - /// pub fn create_instruction( - /// program_id: &Pubkey, - /// from: &Pubkey, - /// to: &Pubkey, - /// lamports: u64, - /// ) -> Instruction { - /// let instr = MyInstruction { lamports }; - /// - /// Instruction::new_with_borsh( - /// *program_id, - /// &instr, - /// vec![ - /// AccountMeta::new(*from, true), - /// AccountMeta::new(*to, false), - /// ], - /// ) - /// } - /// ``` - pub fn new_with_borsh( - program_id: Pubkey, - data: &T, - accounts: Vec, - ) -> Self { - let data = borsh::to_vec(data).unwrap(); - Self { - program_id, - accounts, - data, - } - } - - /// Create a new instruction from a value, encoded with [`bincode`]. - /// - /// [`bincode`]: https://docs.rs/bincode/latest/bincode/ - /// - /// `program_id` is the address of the program that will execute the instruction. - /// `accounts` contains a description of all accounts that may be accessed by the program. - /// - /// # Examples - /// - /// ``` - /// # use solana_program::{ - /// # pubkey::Pubkey, - /// # instruction::{AccountMeta, Instruction}, - /// # }; - /// # use serde::{Serialize, Deserialize}; - /// # - /// #[derive(Serialize, Deserialize)] - /// pub struct MyInstruction { - /// pub lamports: u64, - /// } - /// - /// pub fn create_instruction( - /// program_id: &Pubkey, - /// from: &Pubkey, - /// to: &Pubkey, - /// lamports: u64, - /// ) -> Instruction { - /// let instr = MyInstruction { lamports }; - /// - /// Instruction::new_with_bincode( - /// *program_id, - /// &instr, - /// vec![ - /// AccountMeta::new(*from, true), - /// AccountMeta::new(*to, false), - /// ], - /// ) - /// } - /// ``` - pub fn new_with_bincode( - program_id: Pubkey, - data: &T, - accounts: Vec, - ) -> Self { - let data = serialize(data).unwrap(); - Self { - program_id, - accounts, - data, - } - } - - /// Create a new instruction from a byte slice. - /// - /// `program_id` is the address of the program that will execute the instruction. - /// `accounts` contains a description of all accounts that may be accessed by the program. - /// - /// The caller is responsible for ensuring the correct encoding of `data` as expected - /// by the callee program. - /// - /// # Examples - /// - /// ``` - /// # use solana_program::{ - /// # pubkey::Pubkey, - /// # instruction::{AccountMeta, Instruction}, - /// # }; - /// # use borsh::{io::Error, BorshSerialize, BorshDeserialize}; - /// # - /// #[derive(BorshSerialize, BorshDeserialize)] - /// # #[borsh(crate = "borsh")] - /// pub struct MyInstruction { - /// pub lamports: u64, - /// } - /// - /// pub fn create_instruction( - /// program_id: &Pubkey, - /// from: &Pubkey, - /// to: &Pubkey, - /// lamports: u64, - /// ) -> Result { - /// let instr = MyInstruction { lamports }; - /// - /// let mut instr_in_bytes: Vec = Vec::new(); - /// instr.serialize(&mut instr_in_bytes)?; - /// - /// Ok(Instruction::new_with_bytes( - /// *program_id, - /// &instr_in_bytes, - /// vec![ - /// AccountMeta::new(*from, true), - /// AccountMeta::new(*to, false), - /// ], - /// )) - /// } - /// ``` - pub fn new_with_bytes(program_id: Pubkey, data: &[u8], accounts: Vec) -> Self { - Self { - program_id, - accounts, - data: data.to_vec(), - } - } -} - -/// Addition that returns [`InstructionError::InsufficientFunds`] on overflow. -/// -/// This is an internal utility function. -#[doc(hidden)] -pub fn checked_add(a: u64, b: u64) -> Result { - a.checked_add(b).ok_or(InstructionError::InsufficientFunds) -} - -/// Describes a single account read or written by a program during instruction -/// execution. -/// -/// When constructing an [`Instruction`], a list of all accounts that may be -/// read or written during the execution of that instruction must be supplied. -/// Any account that may be mutated by the program during execution, either its -/// data or metadata such as held lamports, must be writable. -/// -/// Note that because the Solana runtime schedules parallel transaction -/// execution around which accounts are writable, care should be taken that only -/// accounts which actually may be mutated are specified as writable. As the -/// default [`AccountMeta::new`] constructor creates writable accounts, this is -/// a minor hazard: use [`AccountMeta::new_readonly`] to specify that an account -/// is not writable. -#[repr(C)] -#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct AccountMeta { - /// An account's public key. - pub pubkey: Pubkey, - /// True if an `Instruction` requires a `Transaction` signature matching `pubkey`. - pub is_signer: bool, - /// True if the account data or metadata may be mutated during program execution. - pub is_writable: bool, -} - -impl AccountMeta { - /// Construct metadata for a writable account. - /// - /// # Examples - /// - /// ``` - /// # use solana_program::{ - /// # pubkey::Pubkey, - /// # instruction::{AccountMeta, Instruction}, - /// # }; - /// # use borsh::{BorshSerialize, BorshDeserialize}; - /// # - /// # #[derive(BorshSerialize, BorshDeserialize)] - /// # #[borsh(crate = "borsh")] - /// # pub struct MyInstruction; - /// # - /// # let instruction = MyInstruction; - /// # let from = Pubkey::new_unique(); - /// # let to = Pubkey::new_unique(); - /// # let program_id = Pubkey::new_unique(); - /// let instr = Instruction::new_with_borsh( - /// program_id, - /// &instruction, - /// vec![ - /// AccountMeta::new(from, true), - /// AccountMeta::new(to, false), - /// ], - /// ); - /// ``` - pub fn new(pubkey: Pubkey, is_signer: bool) -> Self { - Self { - pubkey, - is_signer, - is_writable: true, - } - } - - /// Construct metadata for a read-only account. - /// - /// # Examples - /// - /// ``` - /// # use solana_program::{ - /// # pubkey::Pubkey, - /// # instruction::{AccountMeta, Instruction}, - /// # }; - /// # use borsh::{BorshSerialize, BorshDeserialize}; - /// # - /// # #[derive(BorshSerialize, BorshDeserialize)] - /// # #[borsh(crate = "borsh")] - /// # pub struct MyInstruction; - /// # - /// # let instruction = MyInstruction; - /// # let from = Pubkey::new_unique(); - /// # let to = Pubkey::new_unique(); - /// # let from_account_storage = Pubkey::new_unique(); - /// # let program_id = Pubkey::new_unique(); - /// let instr = Instruction::new_with_borsh( - /// program_id, - /// &instruction, - /// vec![ - /// AccountMeta::new(from, true), - /// AccountMeta::new(to, false), - /// AccountMeta::new_readonly(from_account_storage, false), - /// ], - /// ); - /// ``` - pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self { - Self { - pubkey, - is_signer, - is_writable: false, - } - } -} - /// A compact encoding of an instruction. /// /// A `CompiledInstruction` is a component of a multi-instruction [`Message`], @@ -672,17 +55,6 @@ impl CompiledInstruction { } } -/// Use to query and convey information about the sibling instruction components -/// when calling the `sol_get_processed_sibling_instruction` syscall. -#[repr(C)] -#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] -pub struct ProcessedSiblingInstruction { - /// Length of the instruction data - pub data_len: u64, - /// Number of AccountMeta structures - pub accounts_len: u64, -} - /// Returns a sibling instruction from the processed sibling instruction list. /// /// The processed sibling instruction list is a reverse-ordered list of @@ -699,10 +71,10 @@ pub fn get_processed_sibling_instruction(index: usize) -> Option { #[cfg(target_os = "solana")] { let mut meta = ProcessedSiblingInstruction::default(); - let mut program_id = Pubkey::default(); + let mut program_id = solana_pubkey::Pubkey::default(); if 1 == unsafe { - crate::syscalls::sol_get_processed_sibling_instruction( + solana_instruction::syscalls::sol_get_processed_sibling_instruction( index as u64, &mut meta, &mut program_id, @@ -716,7 +88,7 @@ pub fn get_processed_sibling_instruction(index: usize) -> Option { accounts.resize_with(meta.accounts_len as usize, AccountMeta::default); let _ = unsafe { - crate::syscalls::sol_get_processed_sibling_instruction( + solana_instruction::syscalls::sol_get_processed_sibling_instruction( index as u64, &mut meta, &mut program_id, @@ -735,16 +107,13 @@ pub fn get_processed_sibling_instruction(index: usize) -> Option { crate::program_stubs::sol_get_processed_sibling_instruction(index) } -// Stack height when processing transaction-level instructions -pub const TRANSACTION_LEVEL_STACK_HEIGHT: usize = 1; - /// Get the current stack height, transaction-level instructions are height /// TRANSACTION_LEVEL_STACK_HEIGHT, fist invoked inner instruction is height /// TRANSACTION_LEVEL_STACK_HEIGHT + 1, etc... pub fn get_stack_height() -> usize { #[cfg(target_os = "solana")] unsafe { - crate::syscalls::sol_get_stack_height() as usize + solana_instruction::syscalls::sol_get_stack_height() as usize } #[cfg(not(target_os = "solana"))] @@ -752,3 +121,12 @@ pub fn get_stack_height() -> usize { crate::program_stubs::sol_get_stack_height() as usize } } + +// TODO: remove this. +/// Addition that returns [`InstructionError::InsufficientFunds`] on overflow. +/// +/// This is an internal utility function. +#[doc(hidden)] +pub fn checked_add(a: u64, b: u64) -> Result { + a.checked_add(b).ok_or(InstructionError::InsufficientFunds) +} diff --git a/sdk/program/src/program_error.rs b/sdk/program/src/program_error.rs index f225561a52d389..9bf25ae7d0ac28 100644 --- a/sdk/program/src/program_error.rs +++ b/sdk/program/src/program_error.rs @@ -3,9 +3,19 @@ #![allow(clippy::arithmetic_side_effects)] #[cfg(feature = "borsh")] use borsh::io::Error as BorshIoError; +pub use solana_instruction::error::{ + ACCOUNT_ALREADY_INITIALIZED, ACCOUNT_BORROW_FAILED, ACCOUNT_DATA_TOO_SMALL, + ACCOUNT_NOT_RENT_EXEMPT, ARITHMETIC_OVERFLOW, BORSH_IO_ERROR, + BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS, CUSTOM_ZERO, ILLEGAL_OWNER, IMMUTABLE, + INCORRECT_AUTHORITY, INCORRECT_PROGRAM_ID, INSUFFICIENT_FUNDS, INVALID_ACCOUNT_DATA, + INVALID_ACCOUNT_DATA_REALLOC, INVALID_ACCOUNT_OWNER, INVALID_ARGUMENT, + INVALID_INSTRUCTION_DATA, INVALID_SEEDS, MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED, + MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED, MAX_SEED_LENGTH_EXCEEDED, MISSING_REQUIRED_SIGNATURES, + NOT_ENOUGH_ACCOUNT_KEYS, UNINITIALIZED_ACCOUNT, UNSUPPORTED_SYSVAR, +}; use { crate::{instruction::InstructionError, msg, pubkey::PubkeyError}, - num_traits::{FromPrimitive, ToPrimitive}, + num_traits::FromPrimitive, solana_decode_error::DecodeError, std::convert::TryFrom, thiserror::Error, @@ -125,46 +135,6 @@ impl PrintProgramError for ProgramError { } } -/// Builtin return values occupy the upper 32 bits -const BUILTIN_BIT_SHIFT: usize = 32; -macro_rules! to_builtin { - ($error:expr) => { - ($error as u64) << BUILTIN_BIT_SHIFT - }; -} - -pub const CUSTOM_ZERO: u64 = to_builtin!(1); -pub const INVALID_ARGUMENT: u64 = to_builtin!(2); -pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3); -pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4); -pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5); -pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6); -pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7); -pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8); -pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9); -pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10); -pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11); -pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12); -pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13); -pub const INVALID_SEEDS: u64 = to_builtin!(14); -pub const BORSH_IO_ERROR: u64 = to_builtin!(15); -pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16); -pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17); -pub const ILLEGAL_OWNER: u64 = to_builtin!(18); -pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19); -pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20); -pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21); -pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(22); -pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(23); -pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(24); -pub const IMMUTABLE: u64 = to_builtin!(25); -pub const INCORRECT_AUTHORITY: u64 = to_builtin!(26); -// Warning: Any new program errors added here must also be: -// - Added to the below conversions -// - Added as an equivalent to InstructionError -// - Be featureized in the BPF loader to return `InstructionError::InvalidError` -// until the feature is activated - impl From for u64 { fn from(error: ProgramError) -> Self { match error { @@ -288,53 +258,6 @@ impl TryFrom for ProgramError { } } -impl From for InstructionError -where - T: ToPrimitive, -{ - fn from(error: T) -> Self { - let error = error.to_u64().unwrap_or(0xbad_c0de); - match error { - CUSTOM_ZERO => Self::Custom(0), - INVALID_ARGUMENT => Self::InvalidArgument, - INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData, - INVALID_ACCOUNT_DATA => Self::InvalidAccountData, - ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall, - INSUFFICIENT_FUNDS => Self::InsufficientFunds, - INCORRECT_PROGRAM_ID => Self::IncorrectProgramId, - MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature, - ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized, - UNINITIALIZED_ACCOUNT => Self::UninitializedAccount, - NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys, - ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed, - MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded, - INVALID_SEEDS => Self::InvalidSeeds, - BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()), - ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt, - UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar, - ILLEGAL_OWNER => Self::IllegalOwner, - MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded, - INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc, - MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded, - BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => { - Self::BuiltinProgramsMustConsumeComputeUnits - } - INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner, - ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow, - IMMUTABLE => Self::Immutable, - INCORRECT_AUTHORITY => Self::IncorrectAuthority, - _ => { - // A valid custom error has no bits set in the upper 32 - if error >> BUILTIN_BIT_SHIFT == 0 { - Self::Custom(error as u32) - } else { - Self::InvalidError - } - } - } - } -} - impl From for ProgramError { fn from(error: PubkeyError) -> Self { match error { diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index 77dabd37c3418c..140d235d3b6861 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -4,10 +4,11 @@ use { crate::{ - account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, - program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::UNSUPPORTED_SYSVAR, + pubkey::Pubkey, }, base64::{prelude::BASE64_STANDARD, Engine}, + solana_instruction::Instruction, solana_program_memory::stubs, std::sync::{Arc, RwLock}, }; diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index 3bf812f6470bbf..221a3a3c652edb 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -1,5 +1,9 @@ #[cfg(target_feature = "static-syscalls")] pub use solana_define_syscall::sys_hash; +#[deprecated(since = "2.1.0", note = "Use `solana_instruction::syscalls` instead")] +pub use solana_instruction::syscalls::{ + sol_get_processed_sibling_instruction, sol_get_stack_height, +}; #[deprecated(since = "2.1.0", note = "Use `solana_msg::sol_log` instead.")] pub use solana_msg::sol_log; #[deprecated( @@ -18,13 +22,7 @@ pub use solana_pubkey::syscalls::{ pub use solana_secp256k1_recover::sol_secp256k1_recover; #[deprecated(since = "2.1.0", note = "Use solana_sha256_hasher::sol_sha256 instead")] pub use solana_sha256_hasher::sol_sha256; -use { - crate::{ - instruction::{AccountMeta, ProcessedSiblingInstruction}, - pubkey::Pubkey, - }, - solana_define_syscall::define_syscall, -}; +use {crate::pubkey::Pubkey, solana_define_syscall::define_syscall}; define_syscall!(fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64)); define_syscall!(fn sol_log_compute_units_()); define_syscall!(fn sol_keccak256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); @@ -34,8 +32,6 @@ define_syscall!(fn sol_invoke_signed_rust(instruction_addr: *const u8, account_i define_syscall!(fn sol_set_return_data(data: *const u8, length: u64)); define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: *mut Pubkey) -> u64); define_syscall!(fn sol_log_data(data: *const u8, data_len: u64)); -define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut ProcessedSiblingInstruction, program_id: *mut Pubkey, data: *mut u8, accounts: *mut AccountMeta) -> u64); -define_syscall!(fn sol_get_stack_height() -> u64); define_syscall!(fn sol_curve_validate_point(curve_id: u64, point_addr: *const u8, result: *mut u8) -> u64); define_syscall!(fn sol_curve_group_op(curve_id: u64, group_op: u64, left_input_addr: *const u8, right_input_addr: *const u8, result_point_addr: *mut u8) -> u64); define_syscall!(fn sol_curve_multiscalar_mul(curve_id: u64, scalars_addr: *const u8, points_addr: *const u8, points_len: u64, result_point_addr: *mut u8) -> u64); diff --git a/sdk/src/transaction/mod.rs b/sdk/src/transaction/mod.rs index ec4ff6a161a007..18a8d1022708d6 100644 --- a/sdk/src/transaction/mod.rs +++ b/sdk/src/transaction/mod.rs @@ -200,7 +200,7 @@ pub struct Transaction { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "5mA54x7skHmXUoVfvwNSDrSo4F8kXJSrDrKrLMcUkAib") + frozen_abi(digest = "H7xQFcd1MtMv9QKZWGatBAXwhg28tpeX59P3s8ZZLAY4") )] #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] pub struct Transaction {