diff --git a/.github/workflows/reusable-tests.yaml b/.github/workflows/reusable-tests.yaml index 7b8af78491..f6ac214058 100644 --- a/.github/workflows/reusable-tests.yaml +++ b/.github/workflows/reusable-tests.yaml @@ -414,6 +414,8 @@ jobs: path: tests/declare-id - cmd: cd tests/declare-program && anchor test --skip-lint path: tests/declare-program + - cmd: cd tests/custom-program && anchor test --skip-lint + path: tests/custom-program - cmd: cd tests/typescript && anchor test --skip-lint && npx tsc --noEmit path: tests/typescript # zero-copy tests cause `/usr/bin/ld: final link failed: No space left on device` diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b357ce94..e32f1d7535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Replace `anchor verify` to use `solana-verify` under the hood, adding automatic installation via AVM, local path support, and future-proof argument passing ([#3768](https://github.com/solana-foundation/anchor/pull/3768)). - lang: Replace `solana-program` crate with smaller crates ([#3819](https://github.com/solana-foundation/anchor/pull/3819)). - cli: Make `anchor deploy` to upload the IDL to the cluster by default unless `--no-idl` is passed ([#3863](https://github.com/solana-foundation/anchor/pull/3863)). +- lang: Add generic program validation support to `Program` type allowing `Program<'info>` for executable-only validation ([#3878](https://github.com/solana-foundation/anchor/pull/3878)). - lang: Use `solana-invoke` instead of `solana_cpi::invoke` ([#3900](https://github.com/solana-foundation/anchor/pull/3900)). - client: remove `solana-client` from `anchor-client` and `cli` ([#3877](https://github.com/solana-foundation/anchor/pull/3877)). - idl: Build IDL on stable Rustc ([#3842](https://github.com/solana-foundation/anchor/pull/3842)). @@ -616,7 +617,7 @@ See the [Anchor 0.29 release notes](https://www.anchor-lang.com/release-notes/0. - spl: Re-export the `spl_token` crate ([#1665](https://github.com/coral-xyz/anchor/pull/1665)). - lang, cli, spl: Update solana toolchain to v1.9.13 ([#1653](https://github.com/coral-xyz/anchor/pull/1653) and [#1751](https://github.com/coral-xyz/anchor/pull/1751)). - lang: `Program` type now deserializes `programdata_address` only on demand ([#1723](https://github.com/coral-xyz/anchor/pull/1723)). -- ts: Make `Provider` an interface and adjust its signatures and add `AnchorProvider` implementor class ([#1707](https://github.com/coral-xyz/anchor/pull/1707)). +- ts: Make `Provider` an interface and adjust its signatures and add `AnchorProvider` implementer class ([#1707](https://github.com/coral-xyz/anchor/pull/1707)). - spl: Change "to" to "from" in `token::burn` ([#1080](https://github.com/coral-xyz/anchor/pull/1080)). ## [0.23.0] - 2022-03-20 diff --git a/Cargo.lock b/Cargo.lock index cbf4f29912..a669adc988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4855,7 +4855,7 @@ dependencies = [ "solana-last-restart-slot 3.0.0", "solana-msg 3.0.0", "solana-native-token 3.0.0", - "solana-program-entrypoint 3.0.0", + "solana-program-entrypoint 3.1.0", "solana-program-error 3.0.0", "solana-program-memory 3.0.0", "solana-program-option 3.0.0", @@ -4889,9 +4889,9 @@ dependencies = [ [[package]] name = "solana-program-entrypoint" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb61aaf3bf54b69721fbaadb0942cfd41f608cf279e514c1362264a24e469a9e" +checksum = "6557cf5b5e91745d1667447438a1baa7823c6086e4ece67f8e6ebfa7a8f72660" dependencies = [ "solana-account-info 3.0.0", "solana-define-syscall 3.0.0", @@ -5799,7 +5799,7 @@ dependencies = [ "solana-hash 3.0.0", "solana-instruction 3.0.0", "solana-last-restart-slot 3.0.0", - "solana-program-entrypoint 3.0.0", + "solana-program-entrypoint 3.1.0", "solana-program-error 3.0.0", "solana-program-memory 3.0.0", "solana-pubkey 3.0.0", diff --git a/README.md b/README.md index e160efe97e..cede1929ac 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To jump straight to examples, go [here](https://github.com/coral-xyz/anchor/tree | `anchor-spl` | CPI clients for SPL programs on Solana | [![crates](https://img.shields.io/crates/v/anchor-spl?color=blue)](https://crates.io/crates/anchor-spl) | [![Docs.rs](https://docs.rs/anchor-spl/badge.svg)](https://docs.rs/anchor-spl) | | `anchor-client` | Rust client for Anchor programs | [![crates](https://img.shields.io/crates/v/anchor-client?color=blue)](https://crates.io/crates/anchor-client) | [![Docs.rs](https://docs.rs/anchor-client/badge.svg)](https://docs.rs/anchor-client) | | `@coral-xyz/anchor` | TypeScript client for Anchor programs | [![npm](https://img.shields.io/npm/v/@coral-xyz/anchor.svg?color=blue)](https://www.npmjs.com/package/@coral-xyz/anchor) | [![Docs](https://img.shields.io/badge/docs-typedoc-blue)](https://coral-xyz.github.io/anchor/ts/index.html) | -| `@coral-xyz/anchor-cli` | CLI to support building and managing an Anchor workspace | [![npm](https://img.shields.io/npm/v/@coral-xyz/anchor-cli.svg?color=blue)](https://www.npmjs.com/package/@coral-xyz/anchor-cli) | [![Docs](https://img.shields.io/badge/docs-typedoc-blue)](https://coral-xyz.github.io/anchor/cli/commands.html) | +| `@coral-xyz/anchor-cli` | CLI to support building and managing an Anchor workspace | [![npm](https://img.shields.io/npm/v/@coral-xyz/anchor-cli.svg?color=blue)](https://www.npmjs.com/package/@coral-xyz/anchor-cli) | [![Docs](https://img.shields.io/badge/docs-typedoc-blue)](https://www.anchor-lang.com/docs/references/cli) | ## Note diff --git a/avm/src/lib.rs b/avm/src/lib.rs index bdc701ccbe..570f4a98aa 100644 --- a/avm/src/lib.rs +++ b/avm/src/lib.rs @@ -753,7 +753,7 @@ mod tests { let expected = vec![version]; assert_eq!(read_installed_versions().unwrap(), expected); - // Should ignore this file because its not anchor- prefixed + // Should ignore this file because it's not anchor- prefixed fs::File::create(AVM_HOME.join("bin").join("garbage").as_path()).unwrap(); assert_eq!(read_installed_versions().unwrap(), expected); } diff --git a/bench/BINARY_SIZE.md b/bench/BINARY_SIZE.md index f975b13418..560194039a 100644 --- a/bench/BINARY_SIZE.md +++ b/bench/BINARY_SIZE.md @@ -18,7 +18,7 @@ Solana version: 2.3.0 | Program | Binary Size | - | | ------- | ----------- | ----------------------- | -| bench | 1,024,096 | 🟢 **-102,744 (9.12%)** | +| bench | 1,024,112 | 🟢 **-102,728 (9.12%)** | ### Notable changes diff --git a/bench/COMPUTE_UNITS.md b/bench/COMPUTE_UNITS.md index 3aa5615ff1..e796228b98 100644 --- a/bench/COMPUTE_UNITS.md +++ b/bench/COMPUTE_UNITS.md @@ -286,9 +286,9 @@ Solana version: 2.1.0 | interface4 | 1,189 | - | | interface8 | 1,748 | - | | program1 | 779 | - | -| program2 | 920 | - | -| program4 | 1,193 | - | -| program8 | 1,744 | - | +| program2 | 934 | 🔴 **+14 (1.52%)** | +| program4 | 1,221 | 🔴 **+28 (2.35%)** | +| program8 | 1,800 | 🔴 **+56 (3.21%)** | | signer1 | 774 | - | | signer2 | 1,064 | - | | signer4 | 1,637 | - | diff --git a/client/src/lib.rs b/client/src/lib.rs index 652f96cafa..a477e67edb 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] //! An RPC client to interact with Solana programs written in [`anchor_lang`]. //! diff --git a/docs/content/docs/basics/cpi.mdx b/docs/content/docs/basics/cpi.mdx index 60e9bf5bd1..4d3d3b6569 100644 --- a/docs/content/docs/basics/cpi.mdx +++ b/docs/content/docs/basics/cpi.mdx @@ -6,7 +6,7 @@ description: --- Cross Program Invocations (CPI) refer to the process of one program invoking -instructions of another program, which enables the composibility of Solana +instructions of another program, which enables the composability of Solana programs. This section will cover the basics of implementing CPIs in an Anchor program, @@ -98,7 +98,7 @@ resulting in a successful SOL transfer. Cross Program Invocations (CPIs) allow one program to invoke instructions on another program. The process of implementing a CPI is the same as that of -creating a instruction where you must specify: +creating an instruction where you must specify: 1. The program ID of the program being called 2. The accounts required by the instruction diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 4956db6e53..fa533896f2 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -468,7 +468,7 @@ To verify that the installation was successful, check the Yarn version: yarn --version ``` -You should the following output: +You should see the following output: ``` 1.22.1 @@ -567,7 +567,7 @@ Keypair Path: /Users/test/.config/solana/id.json Commitment: confirmed ``` -The RPC URL and Websocket URL specific the Solana cluster the CLI will make +The RPC URL and Websocket URL specify the Solana cluster the CLI will make requests to. By default this will be mainnet-beta. You can update the Solana CLI cluster using the following commands: diff --git a/docs/content/docs/tokens/basics/transfer-tokens.mdx b/docs/content/docs/tokens/basics/transfer-tokens.mdx index 39d57d8d47..b09b64942a 100644 --- a/docs/content/docs/tokens/basics/transfer-tokens.mdx +++ b/docs/content/docs/tokens/basics/transfer-tokens.mdx @@ -303,7 +303,7 @@ describe("token-example", () => { console.log("Mint Account", mintAccount); }); - it("Mint Tokens", async () => { + it("Transfer Tokens", async () => { const tx = await program.methods .transferTokens() .accounts({ diff --git a/docs/content/docs/updates/release-notes/0-31-0.mdx b/docs/content/docs/updates/release-notes/0-31-0.mdx index 3317dec46f..0edd2f776e 100644 --- a/docs/content/docs/updates/release-notes/0-31-0.mdx +++ b/docs/content/docs/updates/release-notes/0-31-0.mdx @@ -102,7 +102,7 @@ Program name: `my-program` ### Pass `cargo` args to IDL build Both `anchor build` and `anchor idl build` commands pass the `cargo` arguments -to the underyling IDL build command. For example: +to the underlying IDL build command. For example: ``` anchor build -- --features my-feature @@ -274,7 +274,7 @@ still allows empty discriminators because some non-Anchor programs, e.g. the SPL Token program, don't have account discriminators. In that case, safety checks should never depend on the discriminator. -Additionally, the IDL generation step also checks whether you have ambigious +Additionally, the IDL generation step also checks whether you have ambiguous discriminators i.e. discriminators that can be used for multiple types. However, you should still consider future possibilities, especially when working with 1-byte discriminators. For example, having an account with discriminator `[1]` @@ -440,7 +440,7 @@ Building the IDL via the [`build_idl`](https://github.com/coral-xyz/anchor/blob/v0.31.0/idl/src/build.rs#L119) function made it impossible to extend its functionality e.g. add new parameters without a breaking change. To solve this problem, there is a new way to build -IDLs programatically: +IDLs programmatically: ```rs let idl = IdlBuilder::new().program_path(path).skip_lint(true).build()?; diff --git a/lang/attribute/account/src/lazy.rs b/lang/attribute/account/src/lazy.rs index 10572097a1..e20bbae7f2 100644 --- a/lang/attribute/account/src/lazy.rs +++ b/lang/attribute/account/src/lazy.rs @@ -21,7 +21,7 @@ pub fn gen_lazy(strct: &syn::ItemStruct) -> syn::Result { let load_panic_docs = quote! { /// # Panics /// - /// If there is an existing mutable reference crated by any of the `load_mut` methods. + /// If there is an existing mutable reference created by any of the `load_mut` methods. }; let load_mut_panic_docs = quote! { /// # Panics diff --git a/lang/attribute/program/src/declare_program/mods/internal.rs b/lang/attribute/program/src/declare_program/mods/internal.rs index 1ae08a1674..ffea579c9c 100644 --- a/lang/attribute/program/src/declare_program/mods/internal.rs +++ b/lang/attribute/program/src/declare_program/mods/internal.rs @@ -83,7 +83,7 @@ fn gen_internal_args_mod(idl: &Idl) -> proc_macro2::TokenStream { /// An Anchor generated module containing the program's set of instructions, where each /// method handler in the `#[program]` mod is associated with a struct defining the input /// arguments to the method. These should be used directly, when one wants to serialize - /// Anchor instruction data, for example, when specifying instructions instructions on a + /// Anchor instruction data, for example, when specifying instructions on a /// client. pub mod args { use super::*; diff --git a/lang/src/accounts/program.rs b/lang/src/accounts/program.rs index 122b57a924..f03f3a5395 100644 --- a/lang/src/accounts/program.rs +++ b/lang/src/accounts/program.rs @@ -17,19 +17,28 @@ use std::ops::Deref; /// /// The type has a `programdata_address` function that will return `Option::Some` /// if the program is owned by the [`BPFUpgradeableLoader`](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/index.html) -/// which will contain the `programdata_address` property of the `Program` variant of the [`UpgradeableLoaderState`](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/enum.UpgradeableLoaderState.html) enum. +/// which will contain the `programdata_address` property of the `Program` variant of the [`UpgradeableLoaderState`](https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/state/enum.UpgradeableLoaderState.html) enum. /// /// # Table of Contents /// - [Basic Functionality](#basic-functionality) +/// - [Generic Program Validation](#generic-program-validation) /// - [Out of the Box Types](#out-of-the-box-types) /// /// # Basic Functionality /// +/// For `Program<'info, T>` where T implements Id: /// Checks: /// /// - `account_info.key == expected_program` /// - `account_info.executable == true` /// +/// # Generic Program Validation +/// +/// For `Program<'info>` (without type parameter): +/// - Only checks: `account_info.executable == true` +/// - Use this when you only need to verify that an address is executable, +/// without validating against a specific program ID. +/// /// # Example /// ```ignore /// #[program] @@ -65,6 +74,16 @@ use std::ops::Deref; /// - `program_data`'s constraint checks that its upgrade authority is the `authority` account. /// - Finally, `authority` needs to sign the transaction. /// +/// ## Generic Program Example +/// ```ignore +/// #[derive(Accounts)] +/// pub struct ValidateExecutableProgram<'info> { +/// // Only validates that the provided account is executable +/// pub any_program: Program<'info>, +/// pub authority: Signer<'info>, +/// } +/// ``` +/// /// # Out of the Box Types /// /// Between the [`anchor_lang`](https://docs.rs/anchor-lang/latest/anchor_lang) and [`anchor_spl`](https://docs.rs/anchor_spl/latest/anchor_spl) crates, @@ -75,7 +94,7 @@ use std::ops::Deref; /// - [`Token`](https://docs.rs/anchor-spl/latest/anchor_spl/token/struct.Token.html) /// #[derive(Clone)] -pub struct Program<'info, T> { +pub struct Program<'info, T = ()> { info: &'info AccountInfo<'info>, _phantom: PhantomData, } @@ -128,13 +147,15 @@ impl<'a, T: Id> TryFrom<&'a AccountInfo<'a>> for Program<'a, T> { type Error = Error; /// Deserializes the given `info` into a `Program`. fn try_from(info: &'a AccountInfo<'a>) -> Result { - if info.key != &T::id() { + // Special handling for unit type () - only check executable, not program ID + let is_unit_type = T::id() == Pubkey::default(); + + if !is_unit_type && info.key != &T::id() { return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id()))); } if !info.executable { return Err(ErrorCode::InvalidProgramExecutable.into()); } - Ok(Program::new(info)) } } @@ -195,3 +216,13 @@ impl Key for Program<'_, T> { *self.info.key } } + +// Implement Id trait for unit type to support Program<'info> without type parameter +impl crate::Id for () { + fn id() -> Pubkey { + // For generic programs, this should never be called since they don't validate specific program IDs. + // However, we need to implement it to satisfy the trait bounds. + // Using a special marker value that indicates "any program" + Pubkey::default() + } +} diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 372c1186d2..f640694218 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] //! Anchor ⚓ is a framework for Solana's Sealevel runtime providing several //! convenient developer tools. diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 3c3d17264e..2d9116b7f4 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -1660,7 +1660,7 @@ fn generate_get_token_account_space(mint: &Expr) -> proc_macro2::TokenStream { } } -// Generated code to create an account with with system program with the +// Generated code to create an account with system program with the // given `space` amount of data, owned by `owner`. // // `seeds_with_nonce` should be given for creating PDAs. Otherwise it's an @@ -1729,7 +1729,7 @@ pub fn generate_constraint_executable( let name_str = f.ident.to_string(); let account_ref = generate_account_ref(f); - // because we are only acting on the field, we know it isnt optional at this point + // because we are only acting on the field, we know it isn't optional at this point // as it was unwrapped in `generate_constraint` quote! { if !#account_ref.executable { diff --git a/lang/syn/src/idl/accounts.rs b/lang/syn/src/idl/accounts.rs index a66957bc71..b481281f46 100644 --- a/lang/syn/src/idl/accounts.rs +++ b/lang/syn/src/idl/accounts.rs @@ -146,10 +146,17 @@ fn get_address(acc: &Field) -> TokenStream { match &acc.ty { Ty::Program(_) | Ty::Sysvar(_) => { let ty = acc.account_ty(); - let id_trait = matches!(acc.ty, Ty::Program(_)) - .then(|| quote!(anchor_lang::Id)) - .unwrap_or_else(|| quote!(anchor_lang::solana_program::sysvar::SysvarId)); - quote! { Some(<#ty as #id_trait>::id().to_string()) } + // Check if this is the unit type marker (for generic Program<'info>) + let ty_str = quote!(#ty).to_string(); + if ty_str == "" || ty_str == "__SolanaProgramUnitType" { + // For generic programs, we don't have a specific address + quote! { None } + } else { + let id_trait = matches!(acc.ty, Ty::Program(_)) + .then(|| quote!(anchor_lang::Id)) + .unwrap_or_else(|| quote!(anchor_lang::solana_program::sysvar::SysvarId)); + quote! { Some(<#ty as #id_trait>::id().to_string()) } + } } _ => acc .constraints diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 579380aa46..28eaa025a8 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] pub mod codegen; pub mod parser; @@ -344,6 +344,20 @@ impl Field { Sysvar<#account> } } + Ty::Program(ty) => { + let program = &ty.account_type_path; + // Check if this is the generic Program<'info> (unit type) + let program_str = quote!(#program).to_string(); + if program_str == "__SolanaProgramUnitType" { + quote! { + #container_ty<'info> + } + } else { + quote! { + #container_ty<'info, #program> + } + } + } _ => quote! { #container_ty<#account_ty> }, @@ -543,8 +557,14 @@ impl Field { }, Ty::Program(ty) => { let program = &ty.account_type_path; - quote! { - #program + // Check if this is the special marker for generic Program<'info> (unit type) + let program_str = quote!(#program).to_string(); + if program_str == "__SolanaProgramUnitType" { + quote! {} + } else { + quote! { + #program + } } } Ty::Interface(ty) => { diff --git a/lang/syn/src/parser/accounts/mod.rs b/lang/syn/src/parser/accounts/mod.rs index b823f939a4..83f82d60d8 100644 --- a/lang/syn/src/parser/accounts/mod.rs +++ b/lang/syn/src/parser/accounts/mod.rs @@ -477,7 +477,7 @@ fn parse_interface_account_ty(path: &syn::Path) -> ParseResult ParseResult { - let account_type_path = parse_account(path)?; + let account_type_path = parse_program_account(path)?; Ok(ProgramTy { account_type_path }) } @@ -486,6 +486,52 @@ fn parse_interface_ty(path: &syn::Path) -> ParseResult { Ok(InterfaceTy { account_type_path }) } +// Special parsing function for Program that handles both Program<'info> and Program<'info, T> +fn parse_program_account(path: &syn::Path) -> ParseResult { + let segments = &path.segments[0]; + match &segments.arguments { + syn::PathArguments::AngleBracketed(args) => { + match args.args.len() { + // Program<'info> - only lifetime, no type parameter + 1 => { + // Create a special marker for unit type that gets handled later + use syn::{Path, PathSegment, PathArguments}; + let path_segment = PathSegment { + ident: syn::Ident::new("__SolanaProgramUnitType", proc_macro2::Span::call_site()), + arguments: PathArguments::None, + }; + + Ok(syn::TypePath { + qself: None, + path: Path { + leading_colon: None, + segments: std::iter::once(path_segment).collect(), + }, + }) + } + // Program<'info, T> - lifetime and type + 2 => { + match &args.args[1] { + syn::GenericArgument::Type(syn::Type::Path(ty_path)) => Ok(ty_path.clone()), + _ => Err(ParseError::new( + args.args[1].span(), + "second bracket argument must be a type", + )), + } + } + _ => Err(ParseError::new( + args.args.span(), + "Program must have either just a lifetime (Program<'info>) or a lifetime and type (Program<'info, T>)", + )), + } + } + _ => Err(ParseError::new( + segments.arguments.span(), + "expected angle brackets with lifetime or lifetime and type", + )), + } +} + // TODO: this whole method is a hack. Do something more idiomatic. fn parse_account(mut path: &syn::Path) -> ParseResult { let path_str = parser::tts_to_string(path).replace(' ', ""); diff --git a/lang/syn/src/parser/context.rs b/lang/syn/src/parser/context.rs index 0ffb487984..cafe226f7b 100644 --- a/lang/syn/src/parser/context.rs +++ b/lang/syn/src/parser/context.rs @@ -72,7 +72,7 @@ impl CrateContext { Please add a `/// CHECK:` doc comment explaining why no checks through types are necessary. Alternatively, for reasons like quick prototyping, you may disable the safety checks by using the `skip-lint` option. - See https://www.anchor-lang.com/docs/the-accounts-struct#safety-checks for more information. + See https://www.anchor-lang.com/docs/basics/program-structure#account-validation for more information. "#, ctx.file.canonicalize().unwrap().display(), span.start().line, diff --git a/spl/src/lib.rs b/spl/src/lib.rs index 62d1293ac0..8c609f4c51 100644 --- a/spl/src/lib.rs +++ b/spl/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] //! Anchor CPI wrappers for popular programs in the Solana ecosystem. diff --git a/tests/auction-house/programs/auction-house/src/lib.rs b/tests/auction-house/programs/auction-house/src/lib.rs index dd24b000cb..13b9bcfd7d 100644 --- a/tests/auction-house/programs/auction-house/src/lib.rs +++ b/tests/auction-house/programs/auction-house/src/lib.rs @@ -297,7 +297,7 @@ pub mod auction_house { &treasury_mint.key(), )?; - // make sure you cant get rugged + // make sure you can't get rugged if rec_acct.delegate.is_some() { return err!(ErrorCode::BuyerATACannotHaveDelegate); } @@ -773,7 +773,7 @@ pub mod auction_house { &[auction_house.bump], ]; - // with the native account, the escrow is it's own owner, + // with the native account, the escrow is its own owner, // whereas with token, it is the auction house that is owner. let signer_seeds_for_royalties = if is_native { escrow_signer_seeds @@ -832,7 +832,7 @@ pub mod auction_house { &treasury_mint.key(), )?; - // make sure you cant get rugged + // make sure you can't get rugged if seller_rec_acct.delegate.is_some() { return err!(ErrorCode::SellerATACannotHaveDelegate); } diff --git a/tests/auction-house/tests/auction-house.ts b/tests/auction-house/tests/auction-house.ts index 220ae7faa2..51fa015a4e 100644 --- a/tests/auction-house/tests/auction-house.ts +++ b/tests/auction-house/tests/auction-house.ts @@ -112,7 +112,10 @@ describe("auction-house", () => { mintAuthority: authority, } ); - await getProvider().sendAndConfirm(tx); + await getProvider().sendAndConfirm(tx, [], { + maxRetries: 3, + skipPreflight: true, + }); }); it("Creates token accounts for the NFT", async () => { @@ -184,7 +187,10 @@ describe("auction-house", () => { lamports: 100 * 10 ** 9, }) ); - const txSig = await getProvider().sendAndConfirm(tx); + const txSig = await getProvider().sendAndConfirm(tx, [], { + maxRetries: 3, + skipPreflight: true, + }); console.log("fund buyer:", txSig); }); @@ -421,7 +427,10 @@ describe("auction-house", () => { .instruction() ); - const txSig = await authorityClient.provider.sendAndConfirm(tx); + const txSig = await authorityClient.provider.sendAndConfirm(tx, [], { + maxRetries: 3, + skipPreflight: true, + }); console.log("updateAuctionHouse:", txSig); const newAh = await authorityClient.account.auctionHouse.fetch( diff --git a/tests/bench/bench.json b/tests/bench/bench.json index cbebb7849f..ee31c99c76 100644 --- a/tests/bench/bench.json +++ b/tests/bench/bench.json @@ -1380,9 +1380,9 @@ "interface4": 1189, "interface8": 1748, "program1": 779, - "program2": 920, - "program4": 1193, - "program8": 1744, + "program2": 934, + "program4": 1221, + "program8": 1800, "signer1": 774, "signer2": 1064, "signer4": 1637, @@ -1677,7 +1677,7 @@ "solanaVersion": "2.3.0", "result": { "binarySize": { - "bench": 1024096 + "bench": 1024112 }, "computeUnits": { "accountInfo1": 685, @@ -1752,9 +1752,9 @@ "interface4": 1301, "interface8": 1867, "program1": 890, - "program2": 1035, - "program4": 1313, - "program8": 1879, + "program2": 1051, + "program4": 1345, + "program8": 1943, "signer1": 874, "signer2": 1173, "signer4": 1759, @@ -1859,4 +1859,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/bench/tests/compute-units.ts b/tests/bench/tests/compute-units.ts index 2a964fdaef..222387e408 100644 --- a/tests/bench/tests/compute-units.ts +++ b/tests/bench/tests/compute-units.ts @@ -141,7 +141,10 @@ describe("Compute units", () => { .instruction(); tx.add(createTokenIx, initTokenIx); - await tokenProgram.provider.sendAndConfirm!(tx, [mintKp, tokenKp]); + await tokenProgram.provider.sendAndConfirm!(tx, [mintKp, tokenKp], { + maxRetries: 3, + skipPreflight: true, + }); }); it("AccountInfo", async () => { diff --git a/tests/cpi-returns/tests/cpi-return.ts b/tests/cpi-returns/tests/cpi-return.ts index 0099ef45a2..75c608e599 100644 --- a/tests/cpi-returns/tests/cpi-return.ts +++ b/tests/cpi-returns/tests/cpi-return.ts @@ -28,7 +28,12 @@ describe("CPI return", () => { const cpiReturn = anchor.web3.Keypair.generate(); - const confirmOptions: ConfirmOptions = { commitment: "confirmed" }; + const confirmOptions: ConfirmOptions = { + commitment: "confirmed", + preflightCommitment: "confirmed", + skipPreflight: true, + maxRetries: 3, + }; it("can initialize", async () => { await calleeProgram.methods diff --git a/tests/custom-discriminator/tests/custom-discriminator.ts b/tests/custom-discriminator/tests/custom-discriminator.ts index b87e20594a..e402d46de7 100644 --- a/tests/custom-discriminator/tests/custom-discriminator.ts +++ b/tests/custom-discriminator/tests/custom-discriminator.ts @@ -19,7 +19,10 @@ describe("custom-discriminator", () => { assert(data.equals(Buffer.from(ix.discriminator))); // Verify tx runs - await program.provider.sendAndConfirm!(tx); + await program.provider.sendAndConfirm!(tx, [], { + maxRetries: 3, + skipPreflight: true, + }); }; it("Integer", () => testCommon("int")); diff --git a/tests/custom-program/Anchor.toml b/tests/custom-program/Anchor.toml new file mode 100644 index 0000000000..653b37e73b --- /dev/null +++ b/tests/custom-program/Anchor.toml @@ -0,0 +1,21 @@ +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[programs.localnet] +custom_program = "FdQ5d5kJDidxLP8qBm2d4G47QbDMWk6iWJ3QkYY2UAP7" + +[scripts] +test = "yarn run ts-mocha -t 1000000 tests/*.ts" + +[test.validator] +url = "https://api.mainnet-beta.solana.com" + +[[test.validator.clone]] +address = "9cxLzxjrTeodcbaEU3KCNGE1a4yFZEcdJ7uEXN378S4U" + +[[test.validator.clone]] +address = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY" + +[[test.validator.clone]] +address = "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH" \ No newline at end of file diff --git a/tests/custom-program/Cargo.toml b/tests/custom-program/Cargo.toml new file mode 100644 index 0000000000..97d6280542 --- /dev/null +++ b/tests/custom-program/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +members = [ + "programs/*" +] +resolver = "2" + +[profile.release] +overflow-checks = true diff --git a/tests/custom-program/package.json b/tests/custom-program/package.json new file mode 100644 index 0000000000..ee17d56bd0 --- /dev/null +++ b/tests/custom-program/package.json @@ -0,0 +1,22 @@ +{ + "name": "custom-program", + "version": "0.31.1", + "license": "(MIT OR Apache-2.0)", + "homepage": "https://github.com/coral-xyz/anchor#readme", + "bugs": { + "url": "https://github.com/coral-xyz/anchor/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/coral-xyz/anchor.git" + }, + "engines": { + "node": ">=17" + }, + "scripts": { + "test": "anchor test" + }, + "dependencies": { + "ts-mocha": "^11.1.0" + } +} diff --git a/tests/custom-program/programs/custom-program/Cargo.toml b/tests/custom-program/programs/custom-program/Cargo.toml new file mode 100644 index 0000000000..90212a17c4 --- /dev/null +++ b/tests/custom-program/programs/custom-program/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "custom-program" +version = "0.1.0" +description = "Created with Anchor" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "custom_program" + +[features] +no-entrypoint = [] +cpi = ["no-entrypoint"] +idl-build = ["anchor-lang/idl-build"] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/custom-program/programs/custom-program/Xargo.toml b/tests/custom-program/programs/custom-program/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/custom-program/programs/custom-program/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/custom-program/programs/custom-program/src/lib.rs b/tests/custom-program/programs/custom-program/src/lib.rs new file mode 100644 index 0000000000..56761f8a4e --- /dev/null +++ b/tests/custom-program/programs/custom-program/src/lib.rs @@ -0,0 +1,54 @@ +use anchor_lang::prelude::*; + +declare_id!("FdQ5d5kJDidxLP8qBm2d4G47QbDMWk6iWJ3QkYY2UAP7"); + +pub const CUSTOM_PROGRAM_ID: Pubkey = pubkey!("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY"); +pub const NON_EXECUTABLE_ACCOUNT_ID: Pubkey = + pubkey!("2myyNegEA6pjAHmmEsJC6JdYhW51gwxQW7ZCTWvwaKTk"); +pub const CUSTOM_PROGRAM_ADDRESS: Pubkey = pubkey!("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH"); + +// Define a marker struct for our custom program ID +pub struct CustomProgramMarker; + +impl Id for CustomProgramMarker { + fn id() -> Pubkey { + id() + } +} + +#[program] +mod custom_program { + use super::*; + + pub fn test_program_validation(ctx: Context) -> Result<()> { + // This demonstrates both types of program validation: + // - generic_program: only validates executable (any program) + // - system_program: validates both program ID and executable + msg!( + "Generic program key: {}", + ctx.accounts.generic_program.key() + ); + msg!("System program key: {}", ctx.accounts.system_program.key()); + msg!( + "Custom program key: {}", + ctx.accounts.custom_program_input.key() + ); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct TestProgramValidation<'info> { + /// Generic program - only validates executable (any program ID) + pub generic_program: Program<'info>, + + /// Specific system program - validates both program ID and executable + pub system_program: Program<'info, System>, + + /// Custom program with specific type - validates both program ID and executable + pub custom_program_input: Program<'info, CustomProgramMarker>, + + /// Program with an address constraint - validates both program ID and executable + #[account(address = CUSTOM_PROGRAM_ADDRESS)] + pub custom_program_address: Program<'info>, +} diff --git a/tests/custom-program/tests/custom-program.ts b/tests/custom-program/tests/custom-program.ts new file mode 100644 index 0000000000..8ac4fd2a13 --- /dev/null +++ b/tests/custom-program/tests/custom-program.ts @@ -0,0 +1,86 @@ +import * as anchor from "@coral-xyz/anchor"; +import { AnchorError, Program } from "@coral-xyz/anchor"; +import { CustomProgram } from "../target/types/custom_program"; +import { assert } from "chai"; + +const CUSTOM_PROGRAM_ID = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY"; + +// This was an Executable Data account for our custom program which is not executable +const NON_EXECUTABLE_ACCOUNT_ID = + "9cxLzxjrTeodcbaEU3KCNGE1a4yFZEcdJ7uEXN378S4U"; + +const CUSTOM_PROGRAM_ADDRESS = "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH"; + +describe("custom_program", () => { + anchor.setProvider(anchor.AnchorProvider.local()); + const program = anchor.workspace.CustomProgram as Program; + + it("Should pass test program validation", async () => { + try { + await program.methods + .testProgramValidation() + .accounts({ + genericProgram: new anchor.web3.PublicKey(CUSTOM_PROGRAM_ID), + systemProgram: anchor.web3.SystemProgram.programId, + customProgramInput: program.programId, + customProgramAddress: new anchor.web3.PublicKey( + CUSTOM_PROGRAM_ADDRESS + ), + }) + .rpc(); + assert.ok(true); + } catch (_err) { + assert(false); + } + }); + + it("Should fail test program validation", async () => { + try { + await program.methods + .testProgramValidation() + .accounts({ + genericProgram: new anchor.web3.PublicKey(CUSTOM_PROGRAM_ID), + systemProgram: anchor.web3.SystemProgram.programId, + customProgramInput: program.programId, + customProgramAddress: new anchor.web3.PublicKey( + NON_EXECUTABLE_ACCOUNT_ID + ), + }) + .rpc(); + assert.ok(false); + } catch (_err) { + assert.ok(true); + assert.isTrue(_err instanceof AnchorError); + const err: AnchorError = _err; + assert.strictEqual(err.error.errorCode.number, 3009); + assert.strictEqual( + err.error.errorMessage, + "Program account is not executable" + ); + } + }); + + it("Should fail test program address mismatch", async () => { + try { + await program.methods + .testProgramValidation() + .accounts({ + genericProgram: new anchor.web3.PublicKey(CUSTOM_PROGRAM_ID), + systemProgram: anchor.web3.SystemProgram.programId, + customProgramInput: program.programId, + customProgramAddress: new anchor.web3.PublicKey(CUSTOM_PROGRAM_ID), + }) + .rpc(); + assert.ok(false); + } catch (_err) { + assert.ok(true); + assert.isTrue(_err instanceof AnchorError); + const err: AnchorError = _err; + assert.strictEqual(err.error.errorCode.number, 2012); + assert.strictEqual( + err.error.errorMessage, + "An address constraint was violated" + ); + } + }); +}); diff --git a/tests/custom-program/tsconfig.json b/tests/custom-program/tsconfig.json new file mode 100644 index 0000000000..dc2b28af30 --- /dev/null +++ b/tests/custom-program/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai", "node"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/tests/errors/tests/errors.ts b/tests/errors/tests/errors.ts index 09df19e7e0..e8f322a55b 100644 --- a/tests/errors/tests/errors.ts +++ b/tests/errors/tests/errors.ts @@ -46,6 +46,7 @@ describe("errors", () => { // because we cannot get logs for them (only through overkill `onLogs`) provider.opts.commitment = "confirmed"; anchor.setProvider(provider); + provider.opts.maxRetries = 3; const program = anchor.workspace.Errors as Program; diff --git a/tests/events/tests/events.ts b/tests/events/tests/events.ts index 7cd2333a78..5cd4d43ac2 100644 --- a/tests/events/tests/events.ts +++ b/tests/events/tests/events.ts @@ -46,9 +46,15 @@ describe("Events", () => { }); describe("CPI event", () => { + const config = { + commitment: "confirmed", + preflightCommitment: "confirmed", + skipPreflight: true, + maxRetries: 3, + } as const; + it("Works without accounts being specified", async () => { const tx = await program.methods.testEventCpi().transaction(); - const config = { commitment: "confirmed" } as const; const txHash = await program.provider.sendAndConfirm(tx, [], config); const txResult = await program.provider.connection.getTransaction( txHash, @@ -91,7 +97,7 @@ describe("Events", () => { ); try { - await program.provider.sendAndConfirm(tx, []); + await program.provider.sendAndConfirm(tx, [], config); } catch (e) { if (e.logs.some((log) => log.includes("ConstraintSigner"))) return; console.log(e); diff --git a/tests/idl/tests/new-idl.ts b/tests/idl/tests/new-idl.ts index 2b05515756..ffa5a8084f 100644 --- a/tests/idl/tests/new-idl.ts +++ b/tests/idl/tests/new-idl.ts @@ -211,14 +211,14 @@ describe("New IDL", () => { const pointX = new anchor.BN(1); const pointY = new anchor.BN(2); const named = await testAccountEnum({ named: { pointX, pointY } }); - if (!named.fullEnum.named) throw new Error("Named not crated"); + if (!named.fullEnum.named) throw new Error("Named not created"); assert(named.fullEnum.named.pointX.eq(pointX)); assert(named.fullEnum.named.pointY.eq(pointY)); // Unnamed const tupleArg = [1, 2, 3, 4] as const; const unnamed = await testAccountEnum({ unnamed: tupleArg }); - if (!unnamed.fullEnum.unnamed) throw new Error("Unnamed not crated"); + if (!unnamed.fullEnum.unnamed) throw new Error("Unnamed not created"); assert( Object.entries(unnamed.fullEnum.unnamed).every( ([key, value]) => value === tupleArg[key as keyof typeof tupleArg] @@ -233,7 +233,7 @@ describe("New IDL", () => { unnamedStruct: tupleStructArg, }); if (!unnamedStruct.fullEnum.unnamedStruct) { - throw new Error("Unnamed struct not crated"); + throw new Error("Unnamed struct not created"); } assert.strictEqual( unnamedStruct.fullEnum.unnamedStruct[0].u8, diff --git a/tests/lockup/docs/lockups.md b/tests/lockup/docs/lockups.md index 65727fcdab..109524b397 100644 --- a/tests/lockup/docs/lockups.md +++ b/tests/lockup/docs/lockups.md @@ -91,7 +91,7 @@ if any at all. To create a whitelisted program that receives withdrawals/deposits from/to the Lockup program, one needs to implement the whitelist transfer interface, which assumes nothing about the -`instruction_data` but requires accounts to be provided in a specific [order](https://github.com/project-serum/serum-dex/blob/master/registry/program/src/deposit.rs#L18). +`instruction_data` but requires accounts to be provided in a specific order. Take staking locked tokens as a working example. diff --git a/tests/lockup/docs/staking.md b/tests/lockup/docs/staking.md index 564e32ff44..3f4e7f8b11 100644 --- a/tests/lockup/docs/staking.md +++ b/tests/lockup/docs/staking.md @@ -31,7 +31,7 @@ to understand, contribute to, or modify the code. Accounts are the pieces of state owned by a Solana program. For reference while reading, here are all accounts used by the **Registry** program. -* `Registrar` - Analogous to an SPL token `Mint`, the `Registrar` defines a staking instance. It has its own pool, and it's own set of rewards distributed amongst its own set of stakers. +* `Registrar` - Analogous to an SPL token `Mint`, the `Registrar` defines a staking instance. It has its own pool, and its own set of rewards distributed amongst its own set of stakers. * `Member` - Analogous to an SPL token `Account`, `Member` accounts represent a **beneficiary**'s (i.e. a wallet's) stake state. This account has several vaults, all of which represent the funds belonging to an individual user. * `PendingWithdrawal` - A transfer out of the staking pool (poorly named since it's not a withdrawal out of the program. But a withdrawal out of the staking pool and into a `Member`'s freely available balances). * `RewardVendor` - A reward that has been dropped onto stakers and is distributed pro rata to staked `Member` beneficiaries. @@ -89,7 +89,7 @@ from the stake pool is complete, and the funds are ready to be used again. Feel free to skip this section and jump to the **Reward Vendors** section if you want to just see how rewards work. -One could imagine several ways to drop rewards onto a staking pool, each with there own downsides. +One could imagine several ways to drop rewards onto a staking pool, each with their own downsides. Of course what you want is, for a given reward amount, to atomically snapshot the state of the staking pool and to distribute it proportionally to all stake holders. Effectively, an on chain program such as @@ -116,7 +116,7 @@ This is not auditable or verifiable. And if you want to answer these questions, complex off-chain protocols that require either fancy cryptography or effectively recreating a BFT system off chain. -Another solution considerered was to use a uniswap-style AMM pool (without the swapping). +Another solution considered was to use a uniswap-style AMM pool (without the swapping). This has a lot of advantages. First it's easy to reason about and implement in a single transaction. To drop rewards globally onto the pool, one can deposit funds directly into the pool, in which case the reward is automatically received by owners of the staking pool token upon redemption, a process diff --git a/tests/lockup/programs/lockup/src/lib.rs b/tests/lockup/programs/lockup/src/lib.rs index a5da89cb6a..2c9aac54e3 100644 --- a/tests/lockup/programs/lockup/src/lib.rs +++ b/tests/lockup/programs/lockup/src/lib.rs @@ -126,7 +126,7 @@ pub mod lockup { let cpi_ctx = CpiContext::from(&*ctx.accounts).with_signer(signer); token::transfer(cpi_ctx, amount)?; - // Bookeeping. + // Bookkeeping. let vesting = &mut ctx.accounts.vesting; vesting.outstanding -= amount; diff --git a/tests/optional/tests/optional.ts b/tests/optional/tests/optional.ts index dc4bf4ad30..678f754f79 100644 --- a/tests/optional/tests/optional.ts +++ b/tests/optional/tests/optional.ts @@ -765,7 +765,7 @@ describe("Optional", () => { .signers([dataAccountKeypair3]) .rpc(); assert.fail( - "Unexpected success in creating a transaction that should have failed with `ConstraintRaw` error" + "Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error" ); } catch (e) { // @ts-ignore diff --git a/tests/package.json b/tests/package.json index 41c258ceb9..afb9d34a5b 100644 --- a/tests/package.json +++ b/tests/package.json @@ -16,6 +16,7 @@ "composite", "custom-coder", "custom-discriminator", + "custom-program", "declare-id", "declare-program", "errors", diff --git a/tests/spl/transfer-hook/tests/transfer-hook.ts b/tests/spl/transfer-hook/tests/transfer-hook.ts index 249cded332..e3ad29234f 100644 --- a/tests/spl/transfer-hook/tests/transfer-hook.ts +++ b/tests/spl/transfer-hook/tests/transfer-hook.ts @@ -136,11 +136,12 @@ describe("transfer hook", () => { "confirmed" ); - await sendAndConfirmTransaction(provider.connection, transaction, [ - payer, - mint, - mintAuthority, - ]); + await sendAndConfirmTransaction( + provider.connection, + transaction, + [payer, mint, mintAuthority], + { maxRetries: 3, skipPreflight: true } + ); }); it("can create an `InitializeExtraAccountMetaList` instruction with the proper discriminator", async () => { @@ -275,7 +276,8 @@ describe("transfer hook", () => { await sendAndConfirmTransaction( provider.connection, new Transaction().add(ix), - [payer, sourceAuthority] + [payer, sourceAuthority], + { maxRetries: 3, skipPreflight: true } ); // Check the resulting token balances diff --git a/tests/zero-copy/programs/zero-copy/src/lib.rs b/tests/zero-copy/programs/zero-copy/src/lib.rs index 3db1e5b09b..a71c562e3f 100644 --- a/tests/zero-copy/programs/zero-copy/src/lib.rs +++ b/tests/zero-copy/programs/zero-copy/src/lib.rs @@ -170,7 +170,7 @@ pub struct Event { // traits, so any types in method signatures must as well. // 2. All types for zero copy deserialization are `#[repr(packed)]`. However, // the implementation of AnchorSerialize (i.e. borsh), uses references -// to the fields it serializes. So if we were to just throw tehse derives +// to the fields it serializes. So if we were to just throw these derives // onto the other `Event` struct, we would have references to // `#[repr(packed)]` fields, which is unsafe. To avoid the unsafeness, we // just use a separate type. diff --git a/ts/packages/anchor/tests/events.spec.ts b/ts/packages/anchor/tests/events.spec.ts index 5cfde025fb..9c0b8f661f 100644 --- a/ts/packages/anchor/tests/events.spec.ts +++ b/ts/packages/anchor/tests/events.spec.ts @@ -151,12 +151,12 @@ describe("Events", () => { const logs = [ "Program J2XMGdW2qQLx7rAdwWtSZpTXDgAQ988BLP9QTgUZvm54 invoke [1]", "Program log: Instruction: CancelListing", - "Program log: TRANSFERED SOME TOKENS", + "Program log: TRANSFERRED SOME TOKENS", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2549 of 182795 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program log: TRANSFERED SOME TOKENS", + "Program log: TRANSFERRED SOME TOKENS", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: CloseAccount", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1745 of 176782 compute units", @@ -252,12 +252,12 @@ describe("Events", () => { const logs = [ "Program 5VcVB7jEjdWJBkriXxayCrUUkwfhrPK3rXtnkxxUvMFP invoke [1]", "Program log: Instruction: CancelListing", - "Program log: TRANSFERED SOME TOKENS", + "Program log: TRANSFERRED SOME TOKENS", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2549 of 182795 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program log: TRANSFERED SOME TOKENS", + "Program log: TRANSFERRED SOME TOKENS", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: CloseAccount", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1745 of 176782 compute units", diff --git a/ts/packages/spl-governance/program/lib.rs b/ts/packages/spl-governance/program/lib.rs index 08ac3f0117..7ae438cda8 100644 --- a/ts/packages/spl-governance/program/lib.rs +++ b/ts/packages/spl-governance/program/lib.rs @@ -1275,7 +1275,7 @@ pub enum ProposalState { #[derive(AnchorSerialize, AnchorDeserialize)] pub enum VoteType { /// Single choice vote with mutually exclusive choices - /// In the SingeChoice mode there can ever be a single winner + /// In the SingleChoice mode there can ever be a single winner /// If multiple options score the same highest vote then the Proposal is not resolved and considered as Failed /// Note: Yes/No vote is a single choice (Yes) vote with the deny option (No) SingleChoice, diff --git a/ts/packages/spl-token/program/lib.rs b/ts/packages/spl-token/program/lib.rs index 966238369d..39d88ba113 100644 --- a/ts/packages/spl-token/program/lib.rs +++ b/ts/packages/spl-token/program/lib.rs @@ -438,7 +438,7 @@ pub enum TokenError { #[msg("Invalid number of required signers")] InvalidNumberOfRequiredSigners, /// State is uninitialized. - #[msg("State is unititialized")] + #[msg("State is uninitialized")] UninitializedState, // 10