lang: remove the state and interface attributes (#2285)

This commit is contained in:
Jean Marchand (Exotic Markets) 2022-12-22 17:33:44 +01:00 committed by GitHub
parent 7236c8bb69
commit 38bbb21c33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 147 additions and 3420 deletions

View File

@ -251,10 +251,8 @@ jobs:
path: spl/token-proxy
- cmd: cd tests/multisig && anchor test --skip-lint
path: tests/multisig
- cmd: cd tests/interface && anchor test --skip-lint
path: tests/interface
- cmd: cd tests/lockup && anchor test --skip-lint
path: tests/lockup
# - cmd: cd tests/lockup && anchor test --skip-lint
# path: tests/lockup
- cmd: cd tests/swap/deps/openbook-dex/dex && cargo build-bpf -- --locked && cd ../../../ && anchor test --skip-lint
path: tests/swap
- cmd: cd tests/escrow && anchor test --skip-lint && npx tsc --noEmit

View File

@ -361,10 +361,8 @@ jobs:
path: spl/token-proxy
- cmd: cd tests/multisig && anchor test --skip-lint
path: tests/multisig
- cmd: cd tests/interface && anchor test --skip-lint
path: tests/interface
- cmd: cd tests/lockup && anchor test --skip-lint
path: tests/lockup
# - cmd: cd tests/lockup && anchor test --skip-lint
# path: tests/lockup
- cmd: cd tests/swap/deps/openbook-dex/dex && cargo build-bpf -- --locked && cd ../../../ && anchor test --skip-lint
path: tests/swap
- cmd: cd tests/escrow && anchor test --skip-lint && npx tsc --noEmit

View File

@ -11,12 +11,15 @@ The minor version will be incremented upon a breaking change and the patch versi
## [Unreleased]
### Features
- cli: Add `env` option to verifiable builds ([#2325](https://github.com/coral-xyz/anchor/pull/2325)).
### Fixes
### Breaking
- lang: Remove `state` and `interface` attributes ([#2285](https://github.com/coral-xyz/anchor/pull/2285)).
## [0.26.0] - 2022-12-15
### Features

25
Cargo.lock generated
View File

@ -150,18 +150,6 @@ dependencies = [
"syn 1.0.103",
]
[[package]]
name = "anchor-attribute-interface"
version = "0.26.0"
dependencies = [
"anchor-syn",
"anyhow",
"heck 0.3.3",
"proc-macro2 1.0.47",
"quote 1.0.21",
"syn 1.0.103",
]
[[package]]
name = "anchor-attribute-program"
version = "0.26.0"
@ -173,17 +161,6 @@ dependencies = [
"syn 1.0.103",
]
[[package]]
name = "anchor-attribute-state"
version = "0.26.0"
dependencies = [
"anchor-syn",
"anyhow",
"proc-macro2 1.0.47",
"quote 1.0.21",
"syn 1.0.103",
]
[[package]]
name = "anchor-cli"
version = "0.26.0"
@ -252,9 +229,7 @@ dependencies = [
"anchor-attribute-constant",
"anchor-attribute-error",
"anchor-attribute-event",
"anchor-attribute-interface",
"anchor-attribute-program",
"anchor-attribute-state",
"anchor-derive-accounts",
"arrayref",
"base64 0.13.1",

View File

@ -23,12 +23,8 @@ publish:
sleep 25
cd lang/attribute/error/ && cargo publish && cd ../../../
sleep 25
cd lang/attribute/interface/ && cargo publish && cd ../../../
sleep 25
cd lang/attribute/program/ && cargo publish && cd ../../..
sleep 25
cd lang/attribute/state/ && cargo publish && cd ../../../
sleep 25
cd lang/attribute/event/ && cargo publish && cd ../../../
sleep 25
cd lang/ && cargo publish && cd ../

View File

@ -16,8 +16,8 @@ use optional::accounts::Initialize as OptionalInitialize;
use optional::instruction as optional_instruction;
// The `accounts` and `instructions` modules are generated by the framework.
use basic_4::accounts as basic_4_accounts;
use basic_4::basic_4::Counter as CounterState;
use basic_4::instruction as basic_4_instruction;
use basic_4::Counter as CounterAccount;
use clap::Parser;
// The `accounts` and `instructions` modules are generated by the framework.
use composite::accounts::{Bar, CompositeUpdate, Foo, Initialize};
@ -207,24 +207,28 @@ fn events(client: &Client, pid: Pubkey) -> Result<()> {
pub fn basic_4(client: &Client, pid: Pubkey) -> Result<()> {
let program = client.program(pid);
let authority = program.payer();
let (counter, _) = Pubkey::find_program_address(&[b"counter"], &pid);
// Invoke the state's `new` constructor.
program
.state_request()
.accounts(basic_4_accounts::Auth { authority })
.new(basic_4_instruction::state::New)
.request()
.accounts(basic_4_accounts::Initialize {
counter,
authority,
system_program: system_program::ID,
})
.args(basic_4_instruction::Initialize {})
.send()?;
let counter_account: CounterState = program.state()?;
let counter_account: CounterAccount = program.account(counter)?;
assert_eq!(counter_account.authority, authority);
assert_eq!(counter_account.count, 0);
// Call a state method.
program
.state_request()
.accounts(basic_4_accounts::Auth { authority })
.args(basic_4_instruction::state::Increment)
.request()
.accounts(basic_4_accounts::Increment { counter, authority })
.args(basic_4_instruction::Increment {})
.send()?;
let counter_account: CounterState = program.state()?;
let counter_account: CounterAccount = program.account(counter)?;
assert_eq!(counter_account.authority, authority);
assert_eq!(counter_account.count, 1);

View File

@ -5,7 +5,6 @@ use anchor_lang::solana_program::hash::Hash;
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
use anchor_lang::solana_program::program_error::ProgramError;
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::solana_program::system_program;
use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
use regex::Regex;
use solana_account_decoder::UiAccountEncoding;
@ -112,18 +111,6 @@ impl Program {
self.cfg.cluster.url(),
self.cfg.payer.clone(),
self.cfg.options,
RequestNamespace::Global,
)
}
/// Returns a request builder for program state.
pub fn state_request(&self) -> RequestBuilder {
RequestBuilder::from(
self.program_id,
self.cfg.cluster.url(),
self.cfg.payer.clone(),
self.cfg.options,
RequestNamespace::State { new: false },
)
}
@ -176,10 +163,6 @@ impl Program {
})
}
pub fn state<T: AccountDeserialize>(&self) -> Result<T, ClientError> {
self.account(anchor_lang::__private::state::address(&self.program_id))
}
pub fn rpc(&self) -> RpcClient {
RpcClient::new_with_commitment(
self.cfg.cluster.url().to_string(),
@ -400,18 +383,6 @@ pub struct RequestBuilder<'a> {
// Serialized instruction data for the target RPC.
instruction_data: Option<Vec<u8>>,
signers: Vec<&'a dyn Signer>,
// True if the user is sending a state instruction.
namespace: RequestNamespace,
}
#[derive(PartialEq, Eq)]
pub enum RequestNamespace {
Global,
State {
// True if the request is to the state's new ctor.
new: bool,
},
Interface,
}
impl<'a> RequestBuilder<'a> {
@ -420,7 +391,6 @@ impl<'a> RequestBuilder<'a> {
cluster: &str,
payer: Rc<dyn Signer>,
options: Option<CommitmentConfig>,
namespace: RequestNamespace,
) -> Self {
Self {
program_id,
@ -431,7 +401,6 @@ impl<'a> RequestBuilder<'a> {
instructions: Vec::new(),
instruction_data: None,
signers: Vec::new(),
namespace,
}
}
@ -478,16 +447,6 @@ impl<'a> RequestBuilder<'a> {
self
}
/// Invokes the `#[state]`'s `new` constructor.
#[allow(clippy::wrong_self_convention)]
#[must_use]
pub fn new(mut self, args: impl InstructionData) -> Self {
assert!(self.namespace == RequestNamespace::State { new: false });
self.namespace = RequestNamespace::State { new: true };
self.instruction_data = Some(args.data());
self
}
#[must_use]
pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
self.signers.push(signer);
@ -495,36 +454,12 @@ impl<'a> RequestBuilder<'a> {
}
pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
let mut accounts = match self.namespace {
RequestNamespace::State { new } => match new {
false => vec![AccountMeta::new(
anchor_lang::__private::state::address(&self.program_id),
false,
)],
true => vec![
AccountMeta::new_readonly(self.payer.pubkey(), true),
AccountMeta::new(
anchor_lang::__private::state::address(&self.program_id),
false,
),
AccountMeta::new_readonly(
Pubkey::find_program_address(&[], &self.program_id).0,
false,
),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(self.program_id, false),
],
},
_ => Vec::new(),
};
accounts.extend_from_slice(&self.accounts);
let mut instructions = self.instructions.clone();
if let Some(ix_data) = &self.instruction_data {
instructions.push(Instruction {
program_id: self.program_id,
data: ix_data.clone(),
accounts,
accounts: self.accounts.clone(),
});
}

View File

@ -16,6 +16,6 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "=0.24.1"
anchor-lang = "=0.26.0"
num-traits = "0.2"
num-derive = "0.3"

View File

@ -1,5 +1,5 @@
// #region code
use anchor_lang::prelude::*;
use std::ops::DerefMut;
declare_id!("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr");
@ -7,38 +7,72 @@ declare_id!("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr");
pub mod basic_4 {
use super::*;
#[state]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = ctx.accounts.counter.deref_mut();
let bump = *ctx.bumps.get("counter").ok_or(ErrorCode::CannotGetBump)?;
*counter = Counter {
authority: *ctx.accounts.authority.key,
count: 0,
bump,
};
Ok(())
}
impl Counter {
pub fn new(ctx: Context<Auth>) -> anchor_lang::Result<Self> {
Ok(Self {
authority: *ctx.accounts.authority.key,
count: 0,
})
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.authority.key(),
ctx.accounts.counter.authority,
ErrorCode::Unauthorized
);
pub fn increment(&mut self, ctx: Context<Auth>) -> anchor_lang::Result<()> {
if &self.authority != ctx.accounts.authority.key {
return Err(error!(ErrorCode::Unauthorized));
}
self.count += 1;
Ok(())
}
ctx.accounts.counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Auth<'info> {
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = Counter::SIZE,
seeds = [b"counter"],
bump
)]
counter: Account<'info, Counter>,
#[account(mut)]
authority: Signer<'info>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [b"counter"],
bump = counter.bump
)]
counter: Account<'info, Counter>,
authority: Signer<'info>,
}
// #endregion code
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
pub bump: u8,
}
impl Counter {
pub const SIZE: usize = 8 + 32 + 8 + 1;
}
#[error_code]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
#[msg("Cannot get the bump.")]
CannotGetBump,
}

View File

@ -7,35 +7,45 @@ describe("basic-4", () => {
// Configure the client to use the local cluster.
anchor.setProvider(provider);
const program = anchor.workspace.Basic4;
const program = anchor.workspace.Basic4,
counterSeed = anchor.utils.bytes.utf8.encode("counter");
let counterPubkey;
before(async () => {
[counterPubkey] = await anchor.web3.PublicKey.findProgramAddress(
[counterSeed],
program.programId
);
});
it("Is runs the constructor", async () => {
// #region ctor
// Initialize the program's state struct.
await program.state.rpc.new({
accounts: {
await program.methods
.initialize()
.accounts({
counter: counterPubkey,
authority: provider.wallet.publicKey,
},
});
// #endregion ctor
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Fetch the state struct from the network.
// #region accessor
const state = await program.state.fetch();
// #endregion accessor
const counterAccount = await program.account.counter.fetch(counterPubkey);
assert.ok(state.count.eq(new anchor.BN(0)));
assert.ok(counterAccount.count.eq(new anchor.BN(0)));
});
it("Executes a method on the program", async () => {
// #region instruction
await program.state.rpc.increment({
accounts: {
await program.methods
.increment()
.accounts({
counter: counterPubkey,
authority: provider.wallet.publicKey,
},
});
// #endregion instruction
const state = await program.state.fetch();
assert.ok(state.count.eq(new anchor.BN(1)));
})
.rpc();
const counterAccount = await program.account.counter.fetch(counterPubkey);
assert.ok(counterAccount.count.eq(new anchor.BN(1)));
});
});

View File

@ -19,10 +19,8 @@ anchor-debug = [
"anchor-attribute-constant/anchor-debug",
"anchor-attribute-error/anchor-debug",
"anchor-attribute-event/anchor-debug",
"anchor-attribute-interface/anchor-debug",
"anchor-attribute-program/anchor-debug",
"anchor-attribute-program/anchor-debug",
"anchor-attribute-state/anchor-debug",
"anchor-derive-accounts/anchor-debug"
]
@ -32,8 +30,6 @@ anchor-attribute-account = { path = "./attribute/account", version = "0.26.0" }
anchor-attribute-constant = { path = "./attribute/constant", version = "0.26.0" }
anchor-attribute-error = { path = "./attribute/error", version = "0.26.0" }
anchor-attribute-program = { path = "./attribute/program", version = "0.26.0" }
anchor-attribute-state = { path = "./attribute/state", version = "0.26.0" }
anchor-attribute-interface = { path = "./attribute/interface", version = "0.26.0" }
anchor-attribute-event = { path = "./attribute/event", version = "0.26.0" }
anchor-derive-accounts = { path = "./derive/accounts", version = "0.26.0" }
arrayref = "0.3.6"

View File

@ -1,23 +0,0 @@
[package]
name = "anchor-attribute-interface"
version = "0.26.0"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/coral-xyz/anchor"
license = "Apache-2.0"
description = "Attribute for defining a program interface trait"
rust-version = "1.59"
edition = "2021"
[lib]
proc-macro = true
[features]
anchor-debug = ["anchor-syn/anchor-debug"]
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.26.0" }
heck = "0.3.2"

View File

@ -1,243 +0,0 @@
extern crate proc_macro;
use anchor_syn::parser;
use heck::SnakeCase;
use quote::quote;
use syn::parse_macro_input;
/// The `#[interface]` attribute allows one to define an external program
/// dependency, without having any knowledge about the program, other than
/// the fact that it implements the given trait.
///
/// Additionally, the attribute generates a client that can be used to perform
/// CPI to these external dependencies.
///
/// # Example
///
/// In the following example, we have a counter program, where the count
/// can only be set if the configured external program authorizes it.
///
/// ## Defining an `#[interface]`
///
/// First we define the program that depends on an external interface.
///
/// ```ignore
/// use anchor_lang::prelude::*;
///
/// #[interface]
/// pub trait Auth<'info, T: Accounts<'info>> {
/// fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> anchor_lang::Result<()>;
/// }
///
/// #[program]
/// pub mod counter {
/// use super::*;
///
/// #[state]
/// pub struct Counter {
/// pub count: u64,
/// pub auth_program: Pubkey,
/// }
///
/// impl Counter {
/// pub fn new(_ctx: Context<Empty>, auth_program: Pubkey) -> Result<Self> {
/// Ok(Self {
/// count: 0,
/// auth_program,
/// })
/// }
///
/// #[access_control(SetCount::accounts(&self, &ctx))]
/// pub fn set_count(&mut self, ctx: Context<SetCount>, new_count: u64) -> Result<()> {
/// // Ask the auth program if we should approve the transaction.
/// let cpi_program = ctx.accounts.auth_program.clone();
/// let cpi_ctx = CpiContext::new(cpi_program, Empty {});
///
/// // This is the client generated by the `#[interface]` attribute.
/// auth::is_authorized(cpi_ctx, self.count, new_count)?;
///
/// // Approved, so update.
/// self.count = new_count;
/// Ok(())
/// }
/// }
/// }
///
/// #[derive(Accounts)]
/// pub struct Empty {}
///
/// #[derive(Accounts)]
/// pub struct SetCount<'info> {
/// auth_program: AccountInfo<'info>,
/// }
///
/// impl<'info> SetCount<'info> {
/// pub fn accounts(counter: &Counter, ctx: &Context<SetCount>) -> Result<()> {
/// if ctx.accounts.auth_program.key != &counter.auth_program {
/// return Err(error!(ErrorCode::InvalidAuthProgram));
/// }
/// Ok(())
/// }
/// }
///
/// #[error_code]
/// pub enum ErrorCode {
/// #[msg("Invalid auth program.")]
/// InvalidAuthProgram,
/// }
///```
///
/// ## Defining an implementation
///
/// Now we define the program that implements the interface, which the above
/// program will call.
///
/// ```ignore
/// use anchor_lang::prelude::*;
/// use counter::Auth;
///
/// #[program]
/// pub mod counter_auth {
/// use super::*;
///
/// #[state]
/// pub struct CounterAuth;
///
/// impl<'info> Auth<'info, Empty> for CounterAuth {
/// fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> Result<()> {
/// if current % 2 == 0 {
/// if new % 2 == 0 {
/// return Err(ProgramError::Custom(50).into()); // Arbitrary error code.
/// }
/// } else {
/// if new % 2 == 1 {
/// return Err(ProgramError::Custom(60).into()); // Arbitrary error code.
/// }
/// }
/// Ok(())
/// }
/// }
/// }
/// #[derive(Accounts)]
/// pub struct Empty {}
/// ```
///
/// # Returning Values Across CPI
///
/// The caller above uses a `Result` to act as a boolean. However, in order
/// for this feature to be maximally useful, we need a way to return values from
/// interfaces. For now, one can do this by writing to a shared account, e.g.,
/// with the SPL's [Shared Memory Program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory).
/// In the future, Anchor will add the ability to return values across CPI
/// without having to worry about the details of shared memory accounts.
#[proc_macro_attribute]
pub fn interface(
_args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item_trait = parse_macro_input!(input as syn::ItemTrait);
let trait_name = item_trait.ident.to_string();
let mod_name: proc_macro2::TokenStream = item_trait
.ident
.to_string()
.to_snake_case()
.parse()
.unwrap();
let methods: Vec<proc_macro2::TokenStream> = item_trait
.items
.iter()
.filter_map(|trait_item: &syn::TraitItem| match trait_item {
syn::TraitItem::Method(m) => Some(m),
_ => None,
})
.map(|method: &syn::TraitItemMethod| {
let method_name = &method.sig.ident;
let args: Vec<&syn::PatType> = method
.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => Some(pat_ty),
// TODO: just map this to None once we allow this feature.
_ => panic!("Invalid syntax. No self allowed."),
})
.filter(|pat_ty| {
let mut ty = parser::tts_to_string(&pat_ty.ty);
ty.retain(|s| !s.is_whitespace());
!ty.starts_with("Context<")
})
.collect();
let args_no_tys: Vec<&Box<syn::Pat>> = args
.iter()
.map(|arg| {
&arg.pat
})
.collect();
let args_struct = {
if args.is_empty() {
quote! {
use anchor_lang::prelude::borsh;
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
use anchor_lang::prelude::borsh;
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#args),*
}
}
}
};
let sighash_arr = anchor_syn::codegen::program::common::sighash(&trait_name, &method_name.to_string());
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>(
ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, T>,
#(#args),*
) -> anchor_lang::Result<()> {
#args_struct
let ix = {
let ix = Args {
#(#args_no_tys),*
};
let mut ix_data = anchor_lang::AnchorSerialize::try_to_vec(&ix)
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotSerialize)?;
let mut data = #sighash_tts.to_vec();
data.append(&mut ix_data);
let accounts = ctx.to_account_metas(None);
anchor_lang::solana_program::instruction::Instruction {
program_id: *ctx.program.key,
accounts,
data,
}
};
let mut acc_infos = ctx.to_account_infos();
acc_infos.push(ctx.program.clone());
anchor_lang::solana_program::program::invoke_signed(
&ix,
&acc_infos,
ctx.signer_seeds,
).map_err(Into::into)
}
}
})
.collect();
proc_macro::TokenStream::from(quote! {
#item_trait
/// Anchor generated module for invoking programs implementing an
/// `#[interface]` via CPI.
mod #mod_name {
use super::*;
#(#methods)*
}
})
}

View File

@ -1,22 +0,0 @@
[package]
name = "anchor-attribute-state"
version = "0.26.0"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/coral-xyz/anchor"
license = "Apache-2.0"
description = "Attribute for defining a program state struct"
rust-version = "1.59"
edition = "2021"
[lib]
proc-macro = true
[features]
anchor-debug = ["anchor-syn/anchor-debug"]
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.26.0" }

View File

@ -1,92 +0,0 @@
extern crate proc_macro;
use quote::quote;
use syn::parse_macro_input;
/// The `#[state]` attribute defines the program's state struct, i.e., the
/// program's global account singleton giving the program the illusion of state.
///
/// To allocate space into the account on initialization, pass in the account
/// size into the macro, e.g., `#[state(SIZE)]`. Otherwise, the size of the
/// account returned by the struct's `new` constructor will determine the
/// account size. When determining a size, make sure to reserve enough space
/// for the 8 byte account discriminator prepended to the account. That is,
/// always use 8 extra bytes.
///
/// # Zero Copy Deserialization
///
/// Similar to the `#[account]` attribute one can enable zero copy
/// deserialization by using the `zero_copy` argument:
///
/// ```ignore
/// #[state(zero_copy)]
/// ```
///
/// For more, see the [`account`](./attr.account.html) attribute.
#[deprecated(
since = "0.14.0",
note = "#[state] will be removed in a future version. Use a PDA with static seeds instead"
)]
#[proc_macro_attribute]
pub fn state(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item_struct = parse_macro_input!(input as syn::ItemStruct);
let struct_ident = &item_struct.ident;
let is_zero_copy = args.to_string() == "zero_copy";
let size_override = {
if args.is_empty() {
// No size override given. The account size is whatever is given
// as the initialized value. Use the default implementation.
quote! {
impl anchor_lang::__private::AccountSize for #struct_ident {
fn size(&self) -> anchor_lang::Result<u64> {
Ok(8 + self
.try_to_vec()
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?
.len() as u64)
}
}
}
} else if is_zero_copy {
quote! {
impl anchor_lang::__private::AccountSize for #struct_ident {
fn size(&self) -> anchor_lang::Result<u64> {
let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64;
Ok(8 + len)
}
}
}
} else {
let size = proc_macro2::TokenStream::from(args);
// Size override given to the macro. Use it.
quote! {
impl anchor_lang::__private::AccountSize for #struct_ident {
fn size(&self) -> anchor_lang::Result<u64> {
Ok(#size)
}
}
}
}
};
let attribute = match is_zero_copy {
false => quote! {
#[cfg_attr(feature = "anchor-deprecated-state", account)]
#[cfg_attr(not(feature = "anchor-deprecated-state"), account("state"))]
},
true => quote! {
#[cfg_attr(feature = "anchor-deprecated-state", account(zero_copy))]
#[cfg_attr(not(feature = "anchor-deprecated-state"), account("state", zero_copy))]
},
};
proc_macro::TokenStream::from(quote! {
#attribute
#item_struct
#size_override
})
}

View File

@ -1,149 +0,0 @@
use crate::error::ErrorCode;
#[allow(deprecated)]
use crate::{accounts::state::ProgramState, context::CpiStateContext};
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Result, ToAccountInfos,
ToAccountMetas,
};
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, DerefMut};
/// Boxed container for the program state singleton, used when the state
/// is for a program not currently executing.
#[derive(Clone)]
#[deprecated]
pub struct CpiState<'info, T: AccountSerialize + AccountDeserialize + Clone> {
inner: Box<Inner<'info, T>>,
}
#[derive(Clone)]
struct Inner<'info, T: AccountSerialize + AccountDeserialize + Clone> {
info: AccountInfo<'info>,
account: T,
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T> {
pub fn new(i: AccountInfo<'info>, account: T) -> CpiState<'info, T> {
Self {
inner: Box::new(Inner { info: i, account }),
}
}
/// Deserializes the given `info` into a `CpiState`.
#[inline(never)]
pub fn try_from(info: &AccountInfo<'info>) -> Result<CpiState<'info, T>> {
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(CpiState::new(info.clone(), T::try_deserialize(&mut data)?))
}
fn seed() -> &'static str {
ProgramState::<T>::seed()
}
pub fn address(program_id: &Pubkey) -> Pubkey {
let (base, _nonce) = Pubkey::find_program_address(&[], program_id);
let seed = Self::seed();
let owner = program_id;
Pubkey::create_with_seed(&base, seed, owner).unwrap()
}
/// Convenience api for creating a `CpiStateContext`.
pub fn context<'a, 'b, 'c, A: Accounts<'info>>(
&self,
program: AccountInfo<'info>,
accounts: A,
) -> CpiStateContext<'a, 'b, 'c, 'info, A> {
CpiStateContext::new(program, self.inner.info.clone(), accounts)
}
}
#[allow(deprecated)]
impl<'info, T> Accounts<'info> for CpiState<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
#[inline(never)]
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
// No owner or address check is done here. One must use the
// #[account(state = <account-name>)] constraint.
CpiState::try_from(account)
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for CpiState<'info, T>
{
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.inner.info.is_signer);
let meta = match self.inner.info.is_writable {
false => AccountMeta::new_readonly(*self.inner.info.key, is_signer),
true => AccountMeta::new(*self.inner.info.key, is_signer),
};
vec![meta]
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
for CpiState<'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.inner.info.clone()]
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
for CpiState<'info, T>
{
fn as_ref(&self) -> &AccountInfo<'info> {
&self.inner.info
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Deref for CpiState<'info, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&(self.inner).account
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for CpiState<'info, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut DerefMut::deref_mut(&mut self.inner).account
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
for CpiState<'info, T>
{
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Key for CpiState<'info, T> {
fn key(&self) -> Pubkey {
*self.inner.info.key
}
}

View File

@ -9,9 +9,6 @@ pub mod boxed;
pub mod cpi_account;
#[doc(hidden)]
#[allow(deprecated)]
pub mod cpi_state;
#[doc(hidden)]
#[allow(deprecated)]
pub mod loader;
pub mod option;
pub mod program;
@ -19,9 +16,6 @@ pub mod program;
#[allow(deprecated)]
pub mod program_account;
pub mod signer;
#[doc(hidden)]
#[allow(deprecated)]
pub mod state;
pub mod system_account;
pub mod sysvar;
pub mod unchecked_account;

View File

@ -1,171 +0,0 @@
#[allow(deprecated)]
use crate::accounts::cpi_account::CpiAccount;
use crate::bpf_writer::BpfWriter;
use crate::error::{Error, ErrorCode};
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Result, ToAccountInfo,
ToAccountInfos, ToAccountMetas,
};
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, DerefMut};
pub const PROGRAM_STATE_SEED: &str = "unversioned";
/// Boxed container for the program state singleton.
#[derive(Clone)]
#[deprecated]
pub struct ProgramState<'info, T: AccountSerialize + AccountDeserialize + Clone> {
inner: Box<Inner<'info, T>>,
}
#[derive(Clone)]
struct Inner<'info, T: AccountSerialize + AccountDeserialize + Clone> {
info: AccountInfo<'info>,
account: T,
}
#[allow(deprecated)]
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> {
fn new(info: AccountInfo<'a>, account: T) -> ProgramState<'a, T> {
Self {
inner: Box::new(Inner { info, account }),
}
}
/// Deserializes the given `info` into a `ProgramState`.
#[inline(never)]
pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramState<'a, T>> {
if info.owner != program_id {
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
.with_pubkeys((*info.owner, *program_id)));
}
if info.key != &Self::address(program_id) {
solana_program::msg!("Invalid state address");
return Err(ErrorCode::StateInvalidAddress.into());
}
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(ProgramState::new(
info.clone(),
T::try_deserialize(&mut data)?,
))
}
pub fn seed() -> &'static str {
PROGRAM_STATE_SEED
}
pub fn address(program_id: &Pubkey) -> Pubkey {
address(program_id)
}
}
#[allow(deprecated)]
impl<'info, T> Accounts<'info> for ProgramState<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
#[inline(never)]
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
ProgramState::try_from(program_id, account)
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for ProgramState<'info, T>
{
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.inner.info.is_signer);
let meta = match self.inner.info.is_writable {
false => AccountMeta::new_readonly(*self.inner.info.key, is_signer),
true => AccountMeta::new(*self.inner.info.key, is_signer),
};
vec![meta]
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
for ProgramState<'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.inner.info.clone()]
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
for ProgramState<'info, T>
{
fn as_ref(&self) -> &AccountInfo<'info> {
&self.inner.info
}
}
#[allow(deprecated)]
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for ProgramState<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&(self.inner).account
}
}
#[allow(deprecated)]
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramState<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut DerefMut::deref_mut(&mut self.inner).account
}
}
#[allow(deprecated)]
impl<'info, T> From<CpiAccount<'info, T>> for ProgramState<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
fn from(a: CpiAccount<'info, T>) -> Self {
Self::new(a.to_account_info(), Deref::deref(&a).clone())
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
for ProgramState<'info, T>
{
fn exit(&self, _program_id: &Pubkey) -> Result<()> {
let info = self.to_account_info();
let mut data = info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut writer = BpfWriter::new(dst);
self.inner.account.try_serialize(&mut writer)?;
Ok(())
}
}
pub fn address(program_id: &Pubkey) -> Pubkey {
let (base, _nonce) = Pubkey::find_program_address(&[], program_id);
let seed = PROGRAM_STATE_SEED;
let owner = program_id;
Pubkey::create_with_seed(&base, seed, owner).unwrap()
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Key for ProgramState<'info, T> {
fn key(&self) -> Pubkey {
*self.inner.info.key
}
}

View File

@ -241,85 +241,3 @@ impl<'info, T: ToAccountInfos<'info> + ToAccountMetas> ToAccountMetas
metas
}
}
/// Context specifying non-argument inputs for cross-program-invocations
/// targeted at program state instructions.
#[doc(hidden)]
#[deprecated]
pub struct CpiStateContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
state: AccountInfo<'info>,
cpi_ctx: CpiContext<'a, 'b, 'c, 'info, T>,
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T> {
pub fn new(program: AccountInfo<'info>, state: AccountInfo<'info>, accounts: T) -> Self {
Self {
state,
cpi_ctx: CpiContext {
accounts,
program,
signer_seeds: &[],
remaining_accounts: Vec::new(),
},
}
}
pub fn new_with_signer(
program: AccountInfo<'info>,
state: AccountInfo<'info>,
accounts: T,
signer_seeds: &'a [&'b [&'c [u8]]],
) -> Self {
Self {
state,
cpi_ctx: CpiContext {
accounts,
program,
signer_seeds,
remaining_accounts: Vec::new(),
},
}
}
#[must_use]
pub fn with_signer(mut self, signer_seeds: &'a [&'b [&'c [u8]]]) -> Self {
self.cpi_ctx = self.cpi_ctx.with_signer(signer_seeds);
self
}
pub fn program(&self) -> &AccountInfo<'info> {
&self.cpi_ctx.program
}
pub fn signer_seeds(&self) -> &[&[&[u8]]] {
self.cpi_ctx.signer_seeds
}
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountMetas
for CpiStateContext<'a, 'b, 'c, 'info, T>
{
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
// State account is always first for state instructions.
let mut metas = vec![match self.state.is_writable {
false => AccountMeta::new_readonly(*self.state.key, false),
true => AccountMeta::new(*self.state.key, false),
}];
metas.append(&mut self.cpi_ctx.accounts.to_account_metas(is_signer));
metas
}
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountInfos<'info>
for CpiStateContext<'a, 'b, 'c, 'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
let mut infos = self.cpi_ctx.accounts.to_account_infos();
infos.push(self.state.clone());
infos.push(self.cpi_ctx.program.clone());
infos
}
}

View File

@ -12,7 +12,6 @@ pub const ERROR_CODE_OFFSET: u32 = 6000;
/// - &gt;= 1000 IDL error codes
/// - &gt;= 2000 constraint error codes
/// - &gt;= 3000 account error codes
/// - = 4000 state error code
/// - &gt;= 4100 misc error codes
/// - = 5000 deprecated error code
///
@ -67,8 +66,8 @@ pub enum ErrorCode {
/// 2007 - An executable constraint was violated
#[msg("An executable constraint was violated")]
ConstraintExecutable,
/// 2008 - A state constraint was violated
#[msg("A state constraint was violated")]
/// 2008 - Deprecated Error, feel free to replace with something else
#[msg("Deprecated Error, feel free to replace with something else")]
ConstraintState,
/// 2009 - An associated constraint was violated
#[msg("An associated constraint was violated")]
@ -188,11 +187,6 @@ pub enum ErrorCode {
#[msg("The account was duplicated for more than one reallocation")]
AccountDuplicateReallocs,
// State.
/// 4000 - The given state account does not have the correct address
#[msg("The given state account does not have the correct address")]
StateInvalidAddress = 4000,
// Miscellaneous
/// 4100 - The declared program id does not match actual program id
#[msg("The declared program id does not match the actual program id")]

View File

@ -49,9 +49,7 @@ pub use anchor_attribute_account::{account, declare_id, zero_copy};
pub use anchor_attribute_constant::constant;
pub use anchor_attribute_error::*;
pub use anchor_attribute_event::{emit, event};
pub use anchor_attribute_interface::interface;
pub use anchor_attribute_program::program;
pub use anchor_attribute_state::state;
pub use anchor_derive_accounts::Accounts;
/// Borsh is the default serialization format for instructions and accounts.
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
@ -245,9 +243,9 @@ pub mod prelude {
accounts::account_loader::AccountLoader, accounts::program::Program,
accounts::signer::Signer, accounts::system_account::SystemAccount,
accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
context::Context, context::CpiContext, declare_id, emit, err, error, event, interface,
program, require, require_eq, require_gt, require_gte, require_keys_eq, require_keys_neq,
require_neq, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source, state,
context::Context, context::CpiContext, declare_id, emit, err, error, event, program,
require, require_eq, require_gt, require_gte, require_keys_eq, require_keys_neq,
require_neq, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source,
system_program::System, zero_copy, AccountDeserialize, AccountSerialize, Accounts,
AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, Key, Owner,
ProgramData, Result, ToAccountInfo, ToAccountInfos, ToAccountMetas,
@ -275,7 +273,6 @@ pub mod prelude {
/// Internal module used by macros and unstable apis.
#[doc(hidden)]
pub mod __private {
use super::Result;
/// The discriminator anchor uses to mark an account as closed.
pub const CLOSED_ACCOUNT_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255];
@ -291,17 +288,6 @@ pub mod __private {
use solana_program::pubkey::Pubkey;
pub mod state {
pub use crate::accounts::state::*;
}
// Calculates the size of an account, which may be larger than the deserialized
// data in it. This trait is currently only used for `#[state]` accounts.
#[doc(hidden)]
pub trait AccountSize {
fn size(&self) -> Result<u64>;
}
// Very experimental trait.
#[doc(hidden)]
pub trait ZeroCopyAccessor<Ty> {
@ -318,9 +304,6 @@ pub mod __private {
input.to_bytes()
}
}
#[doc(hidden)]
pub use crate::accounts::state::PROGRAM_STATE_SEED;
}
/// Ensures a condition is true, otherwise returns with the given error.

View File

@ -84,7 +84,6 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
rent_exempt,
seeds,
executable,
state,
close,
address,
associated_token,
@ -128,9 +127,6 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
if let Some(c) = executable {
constraints.push(Constraint::Executable(c));
}
if let Some(c) = state {
constraints.push(Constraint::State(c));
}
if let Some(c) = close {
constraints.push(Constraint::Close(c));
}
@ -163,7 +159,6 @@ fn generate_constraint(
Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
Constraint::Seeds(c) => generate_constraint_seeds(f, c),
Constraint::Executable(c) => generate_constraint_executable(f, c),
Constraint::State(c) => generate_constraint_state(f, c, accs),
Constraint::Close(c) => generate_constraint_close(f, c, accs),
Constraint::Address(c) => generate_constraint_address(f, c),
Constraint::AssociatedToken(c) => generate_constraint_associated_token(f, c, accs),
@ -1142,35 +1137,6 @@ pub fn generate_constraint_executable(
}
}
pub fn generate_constraint_state(
f: &Field,
c: &ConstraintState,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let program_target = c.program_target.clone();
let ident = &f.ident;
let name_str = ident.to_string();
let account_ty = match &f.ty {
Ty::CpiState(ty) => &ty.account_type_path,
_ => panic!("Invalid state constraint"),
};
let program_target_optional_check =
OptionalCheckScope::new_with_field(accs, ident).generate_check(quote! {#program_target});
quote! {
{
#program_target_optional_check
// Checks the given state account is the canonical state account for
// the target program.
if #ident.key() != anchor_lang::accounts::cpi_state::CpiState::<#account_ty>::address(&#program_target.key()) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
}
if AsRef::<AccountInfo>::as_ref(&#ident).owner != &#program_target.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
}
}
}
}
fn generate_custom_error(
account_name: &Ident,
custom_error: &Option<Expr>,

View File

@ -5,30 +5,6 @@ use quote::quote;
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashSet::new();
// Go through state accounts.
if let Some(state) = &program.state {
// Ctor.
if let Some((_ctor, ctor_accounts)) = &state.ctor_and_anchor {
let macro_name = format!(
"__client_accounts_{}",
ctor_accounts.to_string().to_snake_case()
);
accounts.insert(macro_name);
}
// Methods.
if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
for ix in methods {
let anchor_ident = &ix.anchor_ident;
// TODO: move to fn and share with accounts.rs.
let macro_name = format!(
"__client_accounts_{}",
anchor_ident.to_string().to_snake_case()
);
accounts.insert(macro_name);
}
}
}
// Go through instruction accounts.
for ix in &program.ixs {
let anchor_ident = &ix.anchor_ident;

View File

@ -1,11 +1,7 @@
use crate::parser;
use crate::{IxArg, State};
use crate::IxArg;
use heck::CamelCase;
use quote::quote;
// Namespace for calculating state instruction sighash signatures.
pub const SIGHASH_STATE_NAMESPACE: &str = "state";
// Namespace for calculating instruction sighash signatures for any instruction
// not affecting program state.
pub const SIGHASH_GLOBAL_NAMESPACE: &str = "global";
@ -22,10 +18,6 @@ pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
sighash
}
pub fn sighash_ctor() -> [u8; 8] {
sighash(SIGHASH_STATE_NAMESPACE, "new")
}
pub fn generate_ix_variant(name: String, args: &[IxArg]) -> proc_macro2::TokenStream {
let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
let ix_name_camel: proc_macro2::TokenStream = {
@ -45,39 +37,3 @@ pub fn generate_ix_variant(name: String, args: &[IxArg]) -> proc_macro2::TokenSt
}
}
}
pub fn generate_ctor_args(state: &State) -> Vec<syn::Pat> {
generate_ctor_typed_args(state)
.iter()
.map(|pat_ty| *pat_ty.pat.clone())
.collect()
}
pub fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
state
.ctor_and_anchor
.as_ref()
.map(|(ctor, _anchor_ident)| {
ctor.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.clone())
}
_ => {
if !state.is_zero_copy {
panic!("Cannot pass self as parameter")
}
None
}
})
.collect()
})
.unwrap_or_default()
}

View File

@ -1,61 +1,9 @@
use crate::codegen::program::common::{generate_ix_variant, sighash, SIGHASH_GLOBAL_NAMESPACE};
use crate::Program;
use crate::StateIx;
use heck::SnakeCase;
use quote::{quote, ToTokens};
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Generate cpi methods for the state struct.
// The Ctor is not exposed via CPI, since it is a one time use function.
let state_cpi_methods: Vec<proc_macro2::TokenStream> = program
.state
.as_ref()
.map(|state| {
state
.impl_block_and_methods
.as_ref()
.map(|(_, methods)| {
methods
.iter()
.map(|method: &StateIx| {
let accounts_ident = &method.anchor_ident;
let ix_variant = generate_ix_variant(
method.raw_method.sig.ident.to_string(),
&method.args,
);
let method_name = &method.ident;
let args: Vec<&syn::PatType> =
method.args.iter().map(|arg| &arg.raw_arg).collect();
quote! {
pub fn #method_name<'a, 'b, 'c, 'info>(
ctx: anchor_lang::context::CpiStateContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
#(#args),*
) -> anchor_lang::Result<()> {
let ix = {
let ix = instruction::state::#ix_variant;
let data = anchor_lang::InstructionData::data(&ix);
let accounts = ctx.to_account_metas(None);
anchor_lang::solana_program::instruction::Instruction {
program_id: crate::ID,
accounts,
data,
}
};
let mut acc_infos = ctx.to_account_infos();
anchor_lang::solana_program::program::invoke_signed(
&ix,
&acc_infos,
ctx.signer_seeds(),
).map_err(Into::into)
}
}
})
.collect()
})
.unwrap_or_else(Vec::new)
})
.unwrap_or_else(Vec::new);
// Generate cpi methods for global methods.
let global_cpi_methods: Vec<proc_macro2::TokenStream> = program
.ixs
@ -123,11 +71,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
use super::*;
use std::marker::PhantomData;
pub mod state {
use super::*;
#(#state_cpi_methods)*
}
pub struct Return<T> {
phantom: std::marker::PhantomData<T>
@ -150,30 +93,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashSet::new();
// Go through state accounts.
if let Some(state) = &program.state {
// Ctor.
if let Some((_ctor, ctor_accounts)) = &state.ctor_and_anchor {
let macro_name = format!(
"__cpi_client_accounts_{}",
ctor_accounts.to_string().to_snake_case()
);
accounts.insert(macro_name);
}
// Methods.
if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
for ix in methods {
let anchor_ident = &ix.anchor_ident;
// TODO: move to fn and share with accounts.rs.
let macro_name = format!(
"__cpi_client_accounts_{}",
anchor_ident.to_string().to_snake_case()
);
accounts.insert(macro_name);
}
}
}
// Go through instruction accounts.
for ix in &program.ixs {
let anchor_ident = &ix.anchor_ident;

View File

@ -1,110 +1,8 @@
use crate::codegen::program::common::*;
use crate::Program;
use heck::CamelCase;
use quote::quote;
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Dispatch the state constructor.
let ctor_state_dispatch_arm = match &program.state {
None => quote! { /* no-op */ },
Some(state) => match state.ctor_and_anchor.is_some() {
false => quote! {},
true => {
let sighash_arr = sighash_ctor();
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
#sighash_tts => {
__private::__state::__ctor(
program_id,
accounts,
ix_data,
)
}
}
}
},
};
// Dispatch the state impl instructions.
let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(s) => s
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|ix: &crate::StateIx| {
let ix_method_name: proc_macro2::TokenStream =
format!("__{}", ix.raw_method.sig.ident).parse().expect(
"Failed to parse ix method name with `__` as `TokenStream`",
);
let ix_name_camel: proc_macro2::TokenStream = ix
.raw_method
.sig
.ident
.to_string()
.as_str()
.to_camel_case()
.parse()
.expect(
"Failed to parse state ix method name in camel as `TokenStream`",
);
quote! {
instruction::state::#ix_name_camel::DISCRIMINATOR => {
__private::__state::#ix_method_name(
program_id,
accounts,
ix_data,
)
}
}
})
.collect()
})
.unwrap_or_default(),
};
// Dispatch all trait interface implementations.
let trait_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(s) => s
.interfaces
.as_ref()
.map(|interfaces| {
interfaces
.iter()
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.iter()
.map(|m: &crate::StateIx| {
let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
let name = &m.raw_method.sig.ident.to_string();
let ix_method_name: proc_macro2::TokenStream =
format!("__{}_{}", iface.trait_name, name).parse().unwrap();
quote! {
#sighash_tts => {
__private::__interface::#ix_method_name(
program_id,
accounts,
ix_data,
)
}
}
})
.collect::<Vec<proc_macro2::TokenStream>>()
})
.collect()
})
.unwrap_or_default(),
};
// Dispatch all global instructions.
let global_dispatch_arms: Vec<proc_macro2::TokenStream> = program
.ixs
@ -142,11 +40,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
///
/// Sha256("<namespace>:<rust-identifier>")[..8],
///
/// where the namespace can be one of three types. 1) "global" for a
/// regular instruction, 2) "state" for a state struct instruction
/// handler and 3) a trait namespace (used in combination with the
/// `#[interface]` attribute), which is defined by the trait name, e..
/// `MyTrait`.
/// where the namespace can be one type. "global" for a
/// regular instruction.
///
/// With this 8 byte identifier, Anchor performs method dispatch,
/// matching the given 8 byte identifier to the associated method
@ -180,9 +75,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
use anchor_lang::Discriminator;
match sighash {
#ctor_state_dispatch_arm
#(#state_dispatch_arms)*
#(#trait_dispatch_arms)*
#(#global_dispatch_arms)*
_ => {
#fallback_fn

View File

@ -17,13 +17,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
/// code wrapping these user defined methods into something that can be
/// executed on Solana.
///
/// These methods fall into one of three categories, each of which
/// can be considered a different "namespace" of the program.
/// These methods fall into one categorie for now.
///
/// 1) Global methods - regular methods inside of the `#[program]`.
/// 2) State methods - associated methods inside a `#[state]` struct.
/// 3) Interface methods - methods inside a strait struct's
/// implementation of an `#[interface]` trait.
/// Global methods - regular methods inside of the `#[program]`.
///
/// Care must be taken by the codegen to prevent collisions between
/// methods in these different namespaces. For this reason, Anchor uses

View File

@ -1,5 +1,5 @@
use crate::codegen::program::common::*;
use crate::{Program, State};
use crate::Program;
use heck::CamelCase;
use quote::{quote, ToTokens};
@ -193,520 +193,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}
}
};
// Constructor handler.
let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
None => quote! {},
Some(state) => match state.ctor_and_anchor.as_ref() {
None => quote! {},
Some((_ctor, anchor_ident)) => {
let ctor_untyped_args = generate_ctor_args(state);
let name = &state.strct.ident;
let mod_name = &program.name;
let variant_arm = generate_ctor_variant(state);
let ix_name: proc_macro2::TokenStream =
generate_ctor_variant_name().parse().unwrap();
let ix_name_log = format!("Instruction: {}", ix_name);
if state.is_zero_copy {
quote! {
// One time state account initializer. Will faill on subsequent
// invocations.
#[inline(never)]
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction data.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
let mut __bumps = std::collections::BTreeMap::new();
let mut __reallocs = std::collections::BTreeSet::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts =
anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps, &mut __reallocs)?;
let mut ctor_user_def_accounts =
#anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data, &mut __bumps, &mut __reallocs)?;
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
let seed = anchor_lang::__private::PROGRAM_STATE_SEED;
let owner = ctor_accounts.program.key;
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
let space = 8 + std::mem::size_of::<#name>();
let rent = Rent::get()?;
let lamports = rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
let seeds = &[&[nonce][..]];
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
from,
&to,
&base,
seed,
lamports,
space as u64,
owner,
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
ctor_accounts.from.clone(),
ctor_accounts.to.clone(),
ctor_accounts.base.clone(),
ctor_accounts.system_program.clone(),
],
&[seeds],
)?;
// Zero copy deserialize.
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_from_unchecked(program_id, &ctor_accounts.to)?;
// Invoke the ctor in a new lexical scope so that
// the zero-copy RefMut gets dropped. Required
// so that we can subsequently run the exit routine.
{
let mut instance = loader.load_init()?;
instance.new(
anchor_lang::context::Context::new(
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
__bumps,
),
#(#ctor_untyped_args),*
)?;
}
// Exit routines.
ctor_user_def_accounts.exit(program_id)?;
loader.exit(program_id)?;
Ok(())
}
}
} else {
quote! {
// One time state account initializer. Will faill on subsequent
// invocations.
#[inline(never)]
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction data.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
let mut __bumps = std::collections::BTreeMap::new();
let mut __reallocs = std::collections::BTreeSet::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts =
anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps, &mut __reallocs)?;
let mut ctor_user_def_accounts =
#anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data, &mut __bumps, &mut __reallocs)?;
// Invoke the ctor.
let instance = #mod_name::#name::new(
anchor_lang::context::Context::new(
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
__bumps,
),
#(#ctor_untyped_args),*
)?;
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
let seed = anchor_lang::accounts::state::ProgramState::<#name>::seed();
let owner = ctor_accounts.program.key;
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
let space = anchor_lang::__private::AccountSize::size(&instance)?;
let rent = Rent::get()?;
let lamports = rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
let seeds = &[&[nonce][..]];
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
from,
&to,
&base,
seed,
lamports,
space,
owner,
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
ctor_accounts.from.clone(),
ctor_accounts.to.clone(),
ctor_accounts.base.clone(),
ctor_accounts.system_program.clone(),
],
&[seeds],
)?;
// Serialize the state and save it to storage.
ctor_user_def_accounts.exit(program_id)?;
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
instance.try_serialize(&mut cursor)?;
Ok(())
}
}
}
}
},
};
// State method handlers.
let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|ix| {
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let private_ix_method_name: proc_macro2::TokenStream = {
let n = format!("__{}", &ix.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let ix_method_name = &ix.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &ix.anchor_ident;
let name = &state.strct.ident;
let mod_name = &program.name;
let variant_arm =
generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
let ix_name_log = format!("Instruction: {}", ix_name);
if state.is_zero_copy {
quote! {
#[inline(never)]
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
ix_data: &[u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Realloc tracker
let mut __reallocs= std::collections::BTreeSet::new();
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::error::ErrorCode::AccountNotEnoughKeys.into());
}
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps, &mut __reallocs)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
&mut __reallocs,
)?;
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps,
);
// Execute user defined function.
{
let mut state = loader.load_mut()?;
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
}
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
loader.exit(program_id)?;
Ok(())
}
}
} else {
quote! {
#[inline(never)]
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
ix_data: &[u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Realloc tracker.
let mut __reallocs = std::collections::BTreeSet::new();
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::error::ErrorCode::AccountNotEnoughKeys.into());
}
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(
program_id,
&mut remaining_accounts,
&[],
&mut __bumps,
&mut __reallocs,
)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
&mut __reallocs,
)?;
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps
);
// Execute user defined function.
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let acc_info = state.to_account_info();
let mut data = acc_info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
}
})
.collect()
})
.unwrap_or_default(),
};
// State trait handlers.
let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => Vec::new(),
Some(state) => state
.interfaces
.as_ref()
.map(|interfaces| {
interfaces
.iter()
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.iter()
.map(|ix| {
// Easy to implement. Just need to write a test.
// Feel free to open a PR.
assert!(!state.is_zero_copy, "Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let private_ix_method_name: proc_macro2::TokenStream = {
let n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let ix_method_name = &ix.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &ix.anchor_ident;
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
let ix_name_log = format!("Instruction: {}", ix_name);
let raw_args: Vec<&syn::PatType> = ix
.args
.iter()
.map(|arg: &crate::IxArg| &arg.raw_arg)
.collect();
let args_struct = {
if ix.args.is_empty() {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#raw_args),*
}
}
}
};
let deserialize_instruction = quote! {
#args_struct
let ix = Args::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let Args {
#(#ix_arg_names),*
} = ix;
};
if ix.has_receiver {
quote! {
#[inline(never)]
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
ix_data: &[u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction.
#deserialize_instruction
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Realloc tracker.
let mut __reallocs= std::collections::BTreeSet::new();
// Deserialize the program state account.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::error::ErrorCode::AccountNotEnoughKeys.into());
}
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(
program_id,
&mut remaining_accounts,
&[],
&mut __bumps,
&mut __reallocs,
)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
&mut __reallocs,
)?;
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps,
);
// Execute user defined function.
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
// Exit procedures.
accounts.exit(program_id)?;
let acc_info = state.to_account_info();
let mut data = acc_info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
} else {
let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
quote! {
#[inline(never)]
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
ix_data: &[u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
// Deserialize instruction.
#deserialize_instruction
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
let mut __reallocs = std::collections::BTreeSet::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
&mut __reallocs,
)?;
// Execute user defined function.
#state_name::#ix_method_name(
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps
),
#(#ix_arg_names),*
)?;
// Exit procedure.
accounts.exit(program_id)
}
}
}
})
.collect::<Vec<proc_macro2::TokenStream>>()
})
.collect()
})
.unwrap_or_default(),
};
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.ixs
@ -789,21 +275,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#non_inlined_idl
}
/// __state mod defines wrapped handlers for state instructions.
pub mod __state {
use super::*;
#non_inlined_ctor
#(#non_inlined_state_handlers)*
}
/// __interface mod defines wrapped handlers for `#[interface]` trait
/// implementations.
pub mod __interface {
use super::*;
#(#non_inlined_state_trait_handlers)*
}
/// __global mod defines wrapped handlers for global instructions.
pub mod __global {
@ -819,23 +291,3 @@ fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
let n = name.to_camel_case();
n.parse().unwrap()
}
fn generate_ctor_variant_name() -> String {
"New".to_string()
}
fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
let ctor_args = generate_ctor_args(state);
let ctor_variant_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
if ctor_args.is_empty() {
quote! {
#ctor_variant_name
}
} else {
quote! {
#ctor_variant_name {
#(#ctor_args),*
}
}
}
}

View File

@ -5,122 +5,6 @@ use heck::CamelCase;
use quote::quote;
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let ctor_variant = match &program.state {
None => quote! {},
Some(state) => {
let ctor_args: Vec<proc_macro2::TokenStream> = generate_ctor_typed_args(state)
.iter()
.map(|arg| {
format!("pub {}", parser::tts_to_string(arg))
.parse()
.unwrap()
})
.collect();
let strct = {
if ctor_args.is_empty() {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct New;
}
} else {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct New {
#(#ctor_args),*
}
}
}
};
let sighash_arr = sighash_ctor();
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
/// Instruction arguments to the `#[state]`'s `new`
/// constructor.
#strct
impl anchor_lang::Discriminator for New {
const DISCRIMINATOR: [u8; 8] = #sighash_tts;
}
impl anchor_lang::InstructionData for New {}
impl anchor_lang::Owner for New {
fn owner() -> Pubkey {
ID
}
}
}
}
};
let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|method| {
let ix_name_camel: proc_macro2::TokenStream = method
.raw_method
.sig
.ident
.to_string()
.to_camel_case()
.parse()
.unwrap();
let raw_args: Vec<proc_macro2::TokenStream> = method
.args
.iter()
.map(|arg| {
format!("pub {}", parser::tts_to_string(&arg.raw_arg))
.parse()
.unwrap()
})
.collect();
let ix_data_trait = {
let name = method.raw_method.sig.ident.to_string();
let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
impl anchor_lang::Discriminator for #ix_name_camel {
const DISCRIMINATOR: [u8; 8] = #sighash_tts;
}
impl anchor_lang::InstructionData for #ix_name_camel {}
impl anchor_lang::Owner for #ix_name_camel {
fn owner() -> Pubkey {
ID
}
}
}
};
// If no args, output a "unit" variant instead of a struct variant.
if method.args.is_empty() {
quote! {
/// Anchor generated instruction.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel;
#ix_data_trait
}
} else {
quote! {
/// Anchor generated instruction.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel {
#(#raw_args),*
}
#ix_data_trait
}
}
})
.collect()
})
.unwrap_or_default(),
};
let variants: Vec<proc_macro2::TokenStream> = program
.ixs
.iter()
@ -186,13 +70,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
pub mod instruction {
use super::*;
/// Instruction struct definitions for `#[state]` methods.
pub mod state {
use super::*;
#ctor_variant
#(#state_method_variants)*
}
#(#variants)*
}

View File

@ -2,7 +2,7 @@ use crate::idl::*;
use crate::parser::context::CrateContext;
use crate::parser::{self, accounts, docs, error, program};
use crate::Ty;
use crate::{AccountField, AccountsStruct, StateIx};
use crate::{AccountField, AccountsStruct};
use anyhow::Result;
use heck::MixedCase;
use quote::ToTokens;
@ -45,139 +45,6 @@ pub fn parse(
let accs = parse_account_derives(&ctx);
let state = match p.state {
None => None,
Some(state) => match state.ctor_and_anchor {
None => None, // State struct defined but no implementation
Some((ctor, anchor_ident)) => {
let mut methods = state
.impl_block_and_methods
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|method: &StateIx| {
let name = method.ident.to_string().to_mixed_case();
let args = method
.args
.iter()
.map(|arg| {
let mut tts = proc_macro2::TokenStream::new();
arg.raw_arg.ty.to_tokens(&mut tts);
let doc = if !no_docs {
docs::parse(&arg.raw_arg.attrs)
} else {
None
};
let ty = tts.to_string().parse().unwrap();
IdlField {
name: arg.name.to_string().to_mixed_case(),
docs: doc,
ty,
}
})
.collect::<Vec<_>>();
let accounts_strct =
accs.get(&method.anchor_ident.to_string()).unwrap();
let accounts = idl_accounts(
&ctx,
accounts_strct,
&accs,
seeds_feature,
no_docs,
);
IdlInstruction {
name,
docs: None,
accounts,
args,
returns: None,
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let ctor = {
let name = "new".to_string();
let args = ctor
.sig
.inputs
.iter()
.filter(|arg| match arg {
syn::FnArg::Typed(pat_ty) => {
// TODO: this filtering should be done in the parser.
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
!arg_str.starts_with("Context<")
}
_ => false,
})
.map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(arg_typed) => {
let mut tts = proc_macro2::TokenStream::new();
arg_typed.ty.to_tokens(&mut tts);
let doc = if !no_docs {
docs::parse(&arg_typed.attrs)
} else {
None
};
let ty = tts.to_string().parse().unwrap();
IdlField {
name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(),
docs: doc,
ty,
}
}
_ => panic!("Invalid syntax"),
})
.collect();
let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
let accounts =
idl_accounts(&ctx, accounts_strct, &accs, seeds_feature, no_docs);
IdlInstruction {
name,
docs: None,
accounts,
args,
returns: None,
}
};
methods.insert(0, ctor);
let strct = {
let fields = match state.strct.fields {
syn::Fields::Named(f_named) => f_named
.named
.iter()
.map(|f: &syn::Field| {
let mut tts = proc_macro2::TokenStream::new();
f.ty.to_tokens(&mut tts);
let doc = if !no_docs {
docs::parse(&f.attrs)
} else {
None
};
let ty = tts.to_string().parse().unwrap();
IdlField {
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
docs: doc,
ty,
}
})
.collect::<Vec<IdlField>>(),
_ => panic!("State must be a struct"),
};
IdlTypeDefinition {
name: state.name,
docs: None,
ty: IdlTypeDefinitionTy::Struct { fields },
}
};
Some(IdlState { strct, methods })
}
},
};
let error = parse_error_enum(&ctx).map(|mut e| error::parse(&mut e, None));
let error_codes = error.as_ref().map(|e| {
e.codes
@ -292,7 +159,6 @@ pub fn parse(
version,
name: p.name.to_string(),
docs: p.docs.clone(),
state,
instructions,
types,
accounts,

View File

@ -14,8 +14,6 @@ pub struct Idl {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub constants: Vec<IdlConst>,
pub instructions: Vec<IdlInstruction>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub state: Option<IdlState>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub accounts: Vec<IdlTypeDefinition>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]

View File

@ -14,8 +14,8 @@ use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
LitStr, PatType, Token, Type, TypePath,
Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, LitStr, PatType, Token,
Type, TypePath,
};
pub mod codegen;
@ -29,7 +29,6 @@ pub mod parser;
#[derive(Debug)]
pub struct Program {
pub state: Option<State>,
pub ixs: Vec<Ix>,
pub name: Ident,
pub docs: Option<Vec<String>>,
@ -56,32 +55,6 @@ impl ToTokens for Program {
}
}
#[derive(Debug)]
pub struct State {
pub name: String,
pub strct: ItemStruct,
pub ctor_and_anchor: Option<(ImplItemMethod, Ident)>,
pub impl_block_and_methods: Option<(ItemImpl, Vec<StateIx>)>,
pub interfaces: Option<Vec<StateInterface>>,
pub is_zero_copy: bool,
}
#[derive(Debug)]
pub struct StateIx {
pub raw_method: ImplItemMethod,
pub ident: Ident,
pub args: Vec<IxArg>,
pub anchor_ident: Ident,
// True if there exists a &self on the method.
pub has_receiver: bool,
}
#[derive(Debug)]
pub struct StateInterface {
pub trait_name: String,
pub methods: Vec<StateIx>,
}
#[derive(Debug)]
pub struct Ix {
pub raw_method: ItemFn,
@ -449,8 +422,6 @@ impl Field {
anchor_lang::accounts::cpi_account::CpiAccount
},
Ty::Sysvar(_) => quote! { anchor_lang::accounts::sysvar::Sysvar },
Ty::CpiState(_) => quote! { anchor_lang::accounts::cpi_state::CpiState },
Ty::ProgramState(_) => quote! { anchor_lang::accounts::state::ProgramState },
Ty::Program(_) => quote! { anchor_lang::accounts::program::Program },
Ty::AccountInfo => quote! {},
Ty::UncheckedAccount => quote! {},
@ -508,18 +479,6 @@ impl Field {
#ident
}
}
Ty::ProgramState(ty) => {
let account = &ty.account_type_path;
quote! {
#account
}
}
Ty::CpiState(ty) => {
let account = &ty.account_type_path;
quote! {
#account
}
}
Ty::Sysvar(ty) => match ty {
SysvarTy::Clock => quote! {Clock},
SysvarTy::Rent => quote! {Rent},
@ -557,8 +516,6 @@ pub struct CompositeField {
pub enum Ty {
AccountInfo,
UncheckedAccount,
ProgramState(ProgramStateTy),
CpiState(CpiStateTy),
ProgramAccount(ProgramAccountTy),
Loader(LoaderTy),
AccountLoader(AccountLoaderTy),
@ -585,16 +542,6 @@ pub enum SysvarTy {
Rewards,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ProgramStateTy {
pub account_type_path: TypePath,
}
#[derive(Debug, PartialEq, Eq)]
pub struct CpiStateTy {
pub account_type_path: TypePath,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ProgramAccountTy {
// The struct type of the account.
@ -679,7 +626,6 @@ pub struct ConstraintGroup {
rent_exempt: Option<ConstraintRentExempt>,
seeds: Option<ConstraintSeedsGroup>,
executable: Option<ConstraintExecutable>,
state: Option<ConstraintState>,
has_one: Vec<ConstraintHasOne>,
literal: Vec<ConstraintLiteral>,
raw: Vec<ConstraintRaw>,
@ -727,7 +673,6 @@ pub enum Constraint {
Seeds(ConstraintSeedsGroup),
AssociatedToken(ConstraintAssociatedToken),
Executable(ConstraintExecutable),
State(ConstraintState),
Close(ConstraintClose),
Address(ConstraintAddress),
TokenAccount(ConstraintTokenAccountGroup),
@ -750,7 +695,6 @@ pub enum ConstraintToken {
RentExempt(Context<ConstraintRentExempt>),
Seeds(Context<ConstraintSeeds>),
Executable(Context<ConstraintExecutable>),
State(Context<ConstraintState>),
Close(Context<ConstraintClose>),
Payer(Context<ConstraintPayer>),
Space(Context<ConstraintSpace>),
@ -878,11 +822,6 @@ pub struct ConstraintSeeds {
#[derive(Debug, Clone)]
pub struct ConstraintExecutable {}
#[derive(Debug, Clone)]
pub struct ConstraintState {
pub program_target: Ident,
}
#[derive(Debug, Clone)]
pub struct ConstraintPayer {
pub target: Expr,

View File

@ -271,12 +271,6 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
}
},
)),
"state" => ConstraintToken::State(Context::new(
span,
ConstraintState {
program_target: stream.parse()?,
},
)),
"payer" => ConstraintToken::Payer(Context::new(
span,
ConstraintPayer {
@ -340,7 +334,6 @@ pub struct ConstraintGroupBuilder<'ty> {
pub rent_exempt: Option<Context<ConstraintRentExempt>>,
pub seeds: Option<Context<ConstraintSeeds>>,
pub executable: Option<Context<ConstraintExecutable>>,
pub state: Option<Context<ConstraintState>>,
pub payer: Option<Context<ConstraintPayer>>,
pub space: Option<Context<ConstraintSpace>>,
pub close: Option<Context<ConstraintClose>>,
@ -374,7 +367,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
rent_exempt: None,
seeds: None,
executable: None,
state: None,
payer: None,
space: None,
close: None,
@ -575,7 +567,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
rent_exempt,
seeds,
executable,
state,
payer,
space,
close,
@ -724,7 +715,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
owner: into_inner!(owner),
rent_exempt: into_inner!(rent_exempt),
executable: into_inner!(executable),
state: into_inner!(state),
close: into_inner!(close),
address: into_inner!(address),
associated_token: if !is_init { associated_token } else { None },
@ -747,7 +737,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
ConstraintToken::RentExempt(c) => self.add_rent_exempt(c),
ConstraintToken::Seeds(c) => self.add_seeds(c),
ConstraintToken::Executable(c) => self.add_executable(c),
ConstraintToken::State(c) => self.add_state(c),
ConstraintToken::Payer(c) => self.add_payer(c),
ConstraintToken::Space(c) => self.add_space(c),
ConstraintToken::Close(c) => self.add_close(c),
@ -1115,14 +1104,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
Ok(())
}
fn add_state(&mut self, c: Context<ConstraintState>) -> ParseResult<()> {
if self.state.is_some() {
return Err(ParseError::new(c.span(), "state already provided"));
}
self.state.replace(c);
Ok(())
}
fn add_payer(&mut self, c: Context<ConstraintPayer>) -> ParseResult<()> {
if self.init.is_none() {
return Err(ParseError::new(

View File

@ -283,13 +283,11 @@ pub fn parse_account_field(f: &syn::Field) -> ParseResult<AccountField> {
fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
let r = matches!(
ident_string(f)?.0.as_str(),
"ProgramState"
| "ProgramAccount"
"ProgramAccount"
| "CpiAccount"
| "Sysvar"
| "AccountInfo"
| "UncheckedAccount"
| "CpiState"
| "Loader"
| "AccountLoader"
| "Account"
@ -304,8 +302,6 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
fn parse_ty(f: &syn::Field) -> ParseResult<(Ty, bool)> {
let (ident, optional, path) = ident_string(f)?;
let ty = match ident.as_str() {
"ProgramState" => Ty::ProgramState(parse_program_state(&path)?),
"CpiState" => Ty::CpiState(parse_cpi_state(&path)?),
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)?),
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)?),
"Sysvar" => Ty::Sysvar(parse_sysvar(&path)?),
@ -380,20 +376,6 @@ fn ident_string(f: &syn::Field) -> ParseResult<(String, bool, Path)> {
Ok((segments.ident.to_string(), optional, path))
}
fn parse_program_state(path: &syn::Path) -> ParseResult<ProgramStateTy> {
let account_ident = parse_account(path)?;
Ok(ProgramStateTy {
account_type_path: account_ident,
})
}
fn parse_cpi_state(path: &syn::Path) -> ParseResult<CpiStateTy> {
let account_ident = parse_account(path)?;
Ok(CpiStateTy {
account_type_path: account_ident,
})
}
fn parse_cpi_account(path: &syn::Path) -> ParseResult<CpiAccountTy> {
let account_ident = parse_account(path)?;
Ok(CpiAccountTy {

View File

@ -4,14 +4,11 @@ use syn::parse::{Error as ParseError, Result as ParseResult};
use syn::spanned::Spanned;
mod instructions;
mod state;
pub fn parse(program_mod: syn::ItemMod) -> ParseResult<Program> {
let state = state::parse(&program_mod)?;
let docs = docs::parse(&program_mod.attrs);
let (ixs, fallback_fn) = instructions::parse(&program_mod)?;
Ok(Program {
state,
ixs,
name: program_mod.ident.clone(),
docs,

View File

@ -1,315 +0,0 @@
use crate::parser;
use crate::parser::docs;
use crate::parser::program::ctx_accounts_ident;
use crate::{IxArg, State, StateInterface, StateIx};
use syn::parse::{Error as ParseError, Result as ParseResult};
use syn::spanned::Spanned;
// Name of the attribute denoting a state struct.
const STATE_STRUCT_ATTRIBUTE: &str = "state";
// Reserved keyword for the constructor method.
const CTOR_METHOD_NAME: &str = "new";
// Parse the state from the program mod definition.
pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Option<State>> {
let mod_content = &program_mod
.content
.as_ref()
.ok_or_else(|| ParseError::new(program_mod.span(), "program content not provided"))?
.1;
// Parse `struct` marked with the `#[state]` attribute.
let strct: Option<(&syn::ItemStruct, bool)> = mod_content
.iter()
.filter_map(|item| match item {
syn::Item::Struct(item_strct) => {
let attrs = &item_strct.attrs;
if attrs.is_empty() {
return None;
}
let attr_label = attrs[0].path.get_ident().map(|i| i.to_string());
if attr_label != Some(STATE_STRUCT_ATTRIBUTE.to_string()) {
return None;
}
let is_zero_copy = parser::tts_to_string(&attrs[0].tokens) == "(zero_copy)";
Some((item_strct, is_zero_copy))
}
_ => None,
})
.next();
// Parse `impl` block for the state struct.
let impl_block: Option<syn::ItemImpl> = match strct {
None => None,
Some((strct, _)) => mod_content
.iter()
.filter_map(|item| match item {
syn::Item::Impl(item_impl) => {
let impl_ty_str = parser::tts_to_string(&item_impl.self_ty);
let strct_name = strct.ident.to_string();
if item_impl.trait_.is_some() {
return None;
}
if strct_name != impl_ty_str {
return None;
}
Some(item_impl.clone())
}
_ => None,
})
.next(),
};
// Parse ctor and the generic type in `Context<MY-TYPE>`.
let ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)> = impl_block
.as_ref()
.map(|impl_block| {
let r: Option<ParseResult<_>> = impl_block
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => match m.sig.ident == CTOR_METHOD_NAME {
false => None,
true => Some(m),
},
_ => None,
})
.map(|m: &syn::ImplItemMethod| {
let (_, is_zero_copy) = strct
.as_ref()
.expect("impl_block exists therefore the struct exists");
let ctx_arg = {
if *is_zero_copy {
// Second param is context.
let mut iter = m.sig.inputs.iter();
match iter.next() {
None => {
return Err(ParseError::new(
m.sig.span(),
"first parameter must be &mut self",
))
}
Some(arg) => match arg {
syn::FnArg::Receiver(r) => {
if r.mutability.is_none() {
return Err(ParseError::new(
m.sig.span(),
"first parameter must be &mut self",
));
}
}
syn::FnArg::Typed(_) => {
return Err(ParseError::new(
m.sig.span(),
"first parameter must be &mut self",
))
}
},
};
match iter.next() {
None => {
return Err(ParseError::new(
m.sig.span(),
"second parameter must be the Context",
))
}
Some(ctx_arg) => match ctx_arg {
syn::FnArg::Receiver(_) => {
return Err(ParseError::new(
ctx_arg.span(),
"second parameter must be the Context",
))
}
syn::FnArg::Typed(arg) => arg,
},
}
} else {
match m.sig.inputs.first() {
None => {
return Err(ParseError::new(
m.sig.span(),
"first parameter must be the Context",
))
}
Some(ctx_arg) => match ctx_arg {
syn::FnArg::Receiver(_) => {
return Err(ParseError::new(
ctx_arg.span(),
"second parameter must be the Context",
))
}
syn::FnArg::Typed(arg) => arg,
},
}
}
};
Ok((m.clone(), ctx_accounts_ident(ctx_arg)?))
})
.next();
r.transpose()
})
.transpose()?
.unwrap_or(None);
// Parse all methods in the above `impl` block.
let methods: Option<Vec<StateIx>> = impl_block
.as_ref()
.map(|impl_block| {
impl_block
.items
.iter()
.filter_map(|item| match item {
syn::ImplItem::Method(m) => match m.sig.ident != CTOR_METHOD_NAME {
false => None,
true => Some(m),
},
_ => None,
})
.map(|m: &syn::ImplItemMethod| {
let mut args = m
.sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(arg) => Some(arg),
})
.map(|raw_arg| {
let docs = docs::parse(&raw_arg.attrs);
let ident = match &*raw_arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => {
return Err(ParseError::new(
raw_arg.pat.span(),
"unexpected type argument",
))
}
};
Ok(IxArg {
name: ident.clone(),
docs,
raw_arg: raw_arg.clone(),
})
})
.collect::<ParseResult<Vec<IxArg>>>()?;
// Remove the Anchor accounts argument
let anchor = args.remove(0);
let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
Ok(StateIx {
raw_method: m.clone(),
ident: m.sig.ident.clone(),
args,
anchor_ident,
has_receiver: true,
})
})
.collect::<ParseResult<Vec<_>>>()
})
.transpose()?;
// Parse all trait implementations for the above `#[state]` struct.
let trait_impls: Option<Vec<StateInterface>> = strct
.map(|_strct| {
mod_content
.iter()
.filter_map(|item| match item {
syn::Item::Impl(item_impl) => match &item_impl.trait_ {
None => None,
Some((_, path, _)) => {
let trait_name = path
.segments
.iter()
.next()
.expect("Must have one segment in a path")
.ident
.clone()
.to_string();
Some((item_impl, trait_name))
}
},
_ => None,
})
.map(|(item_impl, trait_name)| {
let methods = item_impl
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => Some(m),
_ => None,
})
.map(|m: &syn::ImplItemMethod| {
match m.sig.inputs.first() {
None => Err(ParseError::new(
m.sig.inputs.span(),
"state methods must have a self argument",
)),
Some(_arg) => {
let mut has_receiver = false;
let mut args = m
.sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => {
has_receiver = true;
None
}
syn::FnArg::Typed(arg) => Some(arg),
})
.map(|raw_arg| {
let docs = docs::parse(&raw_arg.attrs);
let ident = match &*raw_arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => panic!("invalid syntax"),
};
IxArg {
name: ident.clone(),
docs,
raw_arg: raw_arg.clone(),
}
})
.collect::<Vec<IxArg>>();
// Remove the Anchor accounts argument
let anchor = args.remove(0);
let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
Ok(StateIx {
raw_method: m.clone(),
ident: m.sig.ident.clone(),
args,
anchor_ident,
has_receiver,
})
}
}
})
.collect::<ParseResult<Vec<StateIx>>>()?;
Ok(StateInterface {
trait_name,
methods,
})
})
.collect::<ParseResult<Vec<StateInterface>>>()
})
.transpose()?;
Ok(strct.map(|(strct, is_zero_copy)| {
// Chop off the `#[state]` attribute. It's just a marker.
//
// TODO: instead of mutating the syntax, we should just implement
// a macro that does nothing.
let mut strct = strct.clone();
strct.attrs = vec![];
State {
name: strct.ident.to_string(),
strct,
interfaces: trait_impls,
impl_block_and_methods: impl_block.map(|impl_block| (impl_block, methods.unwrap())),
ctor_and_anchor,
is_zero_copy,
}
}))
}

View File

@ -1,12 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
counter = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
counter_auth = "Aws2XRVHjNqCUbMmaU245ojT2DBJFYX58KVo2YySEeeP"
[scripts]
test = "yarn run mocha -t 1000000 tests/"
[features]

View File

@ -1,4 +0,0 @@
[workspace]
members = [
"programs/*"
]

View File

@ -1,19 +0,0 @@
{
"name": "interface",
"version": "0.26.0",
"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": ">=11"
},
"scripts": {
"test": "anchor test"
}
}

View File

@ -1,20 +0,0 @@
[package]
name = "counter-auth"
version = "0.1.0"
description = "Created with Anchor"
rust-version = "1.59"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "counter_auth"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = { path = "../../../../lang" }
counter = { path = "../counter", features = ["cpi"] }

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,35 +0,0 @@
//! counter-auth is an example of a program *implementing* an external program
//! interface. Here the `counter::Auth` trait, where we only allow a count
//! to be incremented if it changes the counter from odd -> even or even -> odd.
//! Creative, I know. :P.
use anchor_lang::prelude::*;
use counter::Auth;
declare_id!("Aws2XRVHjNqCUbMmaU245ojT2DBJFYX58KVo2YySEeeP");
#[program]
pub mod counter_auth {
use super::*;
#[state]
pub struct CounterAuth;
impl<'info> Auth<'info, Empty> for CounterAuth {
fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> Result<()> {
if current % 2 == 0 {
if new % 2 == 0 {
return Err(ProgramError::Custom(15000).into()); // Arbitrary error code.
}
} else {
if new % 2 == 1 {
return Err(ProgramError::Custom(16000).into()); // Arbitrary error code.
}
}
Ok(())
}
}
}
#[derive(Accounts)]
pub struct Empty {}

View File

@ -1,19 +0,0 @@
[package]
name = "counter"
version = "0.1.0"
description = "Created with Anchor"
rust-version = "1.59"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "counter"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = { path = "../../../../lang" }

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,73 +0,0 @@
//! counter is an example program that depends on an external interface
//! that another program (here counter-auth/src/lib.rs) must implement. This allows
//! our program to depend on another program, without knowing anything about it
//! other than that it implements the `Auth` trait.
//!
//! Here, we have a counter, where, in order to set the count, the `Auth`
//! program must first approve the transaction.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
#[state]
pub struct Counter {
pub count: u64,
pub auth_program: Pubkey,
}
impl Counter {
pub fn new(_ctx: Context<Empty>, auth_program: Pubkey) -> Result<Self> {
Ok(Self {
count: 0,
auth_program,
})
}
#[access_control(SetCount::accounts(&self, &ctx))]
pub fn set_count(&mut self, ctx: Context<SetCount>, new_count: u64) -> Result<()> {
// Ask the auth program if we should approve the transaction.
let cpi_program = ctx.accounts.auth_program.clone();
let cpi_ctx = CpiContext::new(cpi_program, Empty {});
auth::is_authorized(cpi_ctx, self.count, new_count)?;
// Approved, so update.
self.count = new_count;
Ok(())
}
}
}
#[derive(Accounts)]
pub struct Empty {}
#[derive(Accounts)]
pub struct SetCount<'info> {
auth_program: AccountInfo<'info>,
}
impl<'info> SetCount<'info> {
// Auxiliary account validation requiring program inputs. As a convention,
// we separate it from the business logic of the instruction handler itself.
pub fn accounts(counter: &Counter, ctx: &Context<SetCount>) -> Result<()> {
if ctx.accounts.auth_program.key != &counter.auth_program {
return err!(ErrorCode::InvalidAuthProgram);
}
Ok(())
}
}
#[interface]
pub trait Auth<'info, T: Accounts<'info>> {
fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> Result<()>;
}
#[error_code]
pub enum ErrorCode {
#[msg("Invalid auth program.")]
InvalidAuthProgram,
}

View File

@ -1,46 +0,0 @@
const anchor = require("@coral-xyz/anchor");
const { assert } = require("chai");
const nativeAssert = require("assert");
describe("interface", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const counter = anchor.workspace.Counter;
const counterAuth = anchor.workspace.CounterAuth;
it("Is initialized!", async () => {
await counter.state.rpc.new(counterAuth.programId);
const stateAccount = await counter.state.fetch();
assert.isTrue(stateAccount.count.eq(new anchor.BN(0)));
assert.isTrue(stateAccount.authProgram.equals(counterAuth.programId));
});
it("Should fail to go from even to even", async () => {
await nativeAssert.rejects(
async () => {
await counter.state.rpc.setCount(new anchor.BN(4), {
accounts: {
authProgram: counterAuth.programId,
},
});
},
(err) => {
if (err.toString().split("custom program error: 0x3a98").length !== 2) {
return false;
}
return true;
}
);
});
it("Should succeed to go from even to odd", async () => {
await counter.state.rpc.setCount(new anchor.BN(3), {
accounts: {
authProgram: counterAuth.programId,
},
});
const stateAccount = await counter.state.fetch();
assert.isTrue(stateAccount.count.eq(new anchor.BN(3)));
});
});

View File

@ -4,7 +4,6 @@ wallet = "~/.config/solana/id.json"
[programs.localnet]
misc = "3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh"
misc2 = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
misc_optional = "FNqz6pqLAwvMSds2FYjR4nKV3moVpPNtvkfGFrqLKrgG"
idl_doc = "BqmKjZGVa8fqyWuojJzG16zaKSV1GjAisZToNuvEaz6m"
init_if_needed = "BZoppwWi6jMnydnUBEJzotgEXHwLr3b3NramJgZtWeF2"

View File

@ -18,5 +18,4 @@ default = []
[dependencies]
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
anchor-spl = { path = "../../../../spl" }
misc2 = { path = "../misc2", features = ["cpi"] }
spl-associated-token-account = "1.1.1"

View File

@ -1,10 +1,8 @@
use crate::account::*;
use anchor_lang::accounts::cpi_state::CpiState;
use anchor_lang::accounts::loader::Loader;
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{Mint, Token, TokenAccount};
use misc2::misc2::MyState as Misc2State;
#[derive(Accounts)]
pub struct TestTokenSeedsInit<'info> {
@ -120,12 +118,6 @@ pub struct TestPdaMutZeroCopy<'info> {
pub my_payer: Option<AccountInfo<'info>>,
}
#[derive(Accounts)]
pub struct Ctor {}
#[derive(Accounts)]
pub struct RemainingAccounts {}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(zero)]
@ -160,18 +152,6 @@ pub struct TestExecutable<'info> {
pub program: Option<AccountInfo<'info>>,
}
#[derive(Accounts)]
pub struct TestStateCpi<'info> {
#[account(signer)]
/// CHECK:
pub authority: Option<AccountInfo<'info>>,
#[account(mut, state = misc2_program)]
pub cpi_state: Option<CpiState<'info, Misc2State>>,
#[account(executable)]
/// CHECK:
pub misc2_program: Option<AccountInfo<'info>>,
}
#[derive(Accounts)]
pub struct TestClose<'info> {
#[account(mut, close = sol_dest)]

View File

@ -5,7 +5,6 @@ use account::MAX_SIZE;
use anchor_lang::prelude::*;
use context::*;
use event::*;
use misc2::Auth;
mod account;
mod context;
@ -23,26 +22,6 @@ pub const NO_IDL: u16 = 55;
pub mod misc_optional {
use super::*;
pub const SIZE: u64 = 99;
#[state(SIZE)]
pub struct MyState {
pub v: Vec<u8>,
}
impl MyState {
pub fn new(_ctx: Context<Ctor>) -> Result<Self> {
Ok(Self { v: vec![] })
}
pub fn remaining_accounts(&mut self, ctx: Context<RemainingAccounts>) -> Result<()> {
if ctx.remaining_accounts.len() != 1 {
return Err(ProgramError::Custom(1).into()); // Arbitrary error.
}
Ok(())
}
}
pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> Result<()> {
ctx.accounts.data.as_mut().unwrap().udata = udata;
ctx.accounts.data.as_mut().unwrap().idata = idata;
@ -65,20 +44,6 @@ pub mod misc_optional {
Ok(())
}
pub fn test_state_cpi(ctx: Context<TestStateCpi>, data: u64) -> Result<()> {
let cpi_program = ctx.accounts.misc2_program.as_ref().unwrap().clone();
let cpi_accounts = Auth {
authority: ctx.accounts.authority.as_ref().unwrap().clone(),
};
let ctx = ctx
.accounts
.cpi_state
.as_ref()
.unwrap()
.context(cpi_program, cpi_accounts);
misc2::cpi::state::set_data(ctx, data)
}
pub fn test_u16(ctx: Context<TestU16>, data: u16) -> Result<()> {
ctx.accounts.my_account.as_mut().unwrap().data = data;
Ok(())

View File

@ -18,5 +18,4 @@ default = []
[dependencies]
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
anchor-spl = { path = "../../../../spl" }
misc2 = { path = "../misc2", features = ["cpi"] }
spl-associated-token-account = "1.1.1"

View File

@ -1,10 +1,8 @@
use crate::account::*;
use anchor_lang::accounts::cpi_state::CpiState;
use anchor_lang::accounts::loader::Loader;
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{Mint, Token, TokenAccount};
use misc2::misc2::MyState as Misc2State;
#[derive(Accounts)]
pub struct TestTokenSeedsInit<'info> {
@ -160,18 +158,6 @@ pub struct TestExecutable<'info> {
pub program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestStateCpi<'info> {
#[account(signer)]
/// CHECK:
pub authority: AccountInfo<'info>,
#[account(mut, state = misc2_program)]
pub cpi_state: CpiState<'info, Misc2State>,
#[account(executable)]
/// CHECK:
pub misc2_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestClose<'info> {
#[account(mut, close = sol_dest)]

View File

@ -5,7 +5,6 @@ use account::MAX_SIZE;
use anchor_lang::prelude::*;
use context::*;
use event::*;
use misc2::Auth;
mod account;
mod context;
@ -27,26 +26,6 @@ pub const NO_IDL: u16 = 55;
pub mod misc {
use super::*;
pub const SIZE: u64 = 99;
#[state(SIZE)]
pub struct MyState {
pub v: Vec<u8>,
}
impl MyState {
pub fn new(_ctx: Context<Ctor>) -> Result<Self> {
Ok(Self { v: vec![] })
}
pub fn remaining_accounts(&mut self, ctx: Context<RemainingAccounts>) -> Result<()> {
if ctx.remaining_accounts.len() != 1 {
return Err(ProgramError::Custom(1).into()); // Arbitrary error.
}
Ok(())
}
}
pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> Result<()> {
ctx.accounts.data.udata = udata;
ctx.accounts.data.idata = idata;
@ -69,15 +48,6 @@ pub mod misc {
Ok(())
}
pub fn test_state_cpi(ctx: Context<TestStateCpi>, data: u64) -> Result<()> {
let cpi_program = ctx.accounts.misc2_program.clone();
let cpi_accounts = Auth {
authority: ctx.accounts.authority.clone(),
};
let ctx = ctx.accounts.cpi_state.context(cpi_program, cpi_accounts);
misc2::cpi::state::set_data(ctx, data)
}
pub fn test_u16(ctx: Context<TestU16>, data: u16) -> Result<()> {
ctx.accounts.my_account.data = data;
Ok(())

View File

@ -1,19 +0,0 @@
[package]
name = "misc2"
version = "0.1.0"
description = "Created with Anchor"
rust-version = "1.59"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "misc2"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = { path = "../../../../lang" }

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,38 +0,0 @@
use anchor_lang::prelude::*;
declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
#[program]
pub mod misc2 {
use super::*;
#[state]
pub struct MyState {
pub data: u64,
pub auth: Pubkey,
}
impl MyState {
pub fn new(ctx: Context<Auth>) -> Result<Self> {
Ok(Self {
data: 0,
auth: *ctx.accounts.authority.key,
})
}
pub fn set_data(&mut self, ctx: Context<Auth>, data: u64) -> Result<()> {
if self.auth != *ctx.accounts.authority.key {
return Err(ProgramError::Custom(1234).into()); // Arbitrary error code.
}
self.data = data;
Ok(())
}
}
}
#[derive(Accounts)]
pub struct Auth<'info> {
#[account(signer)]
/// CHECK:
pub authority: AccountInfo<'info>,
}

View File

@ -1,18 +1,9 @@
import * as anchor from "@coral-xyz/anchor";
import {
Program,
BN,
IdlAccounts,
AnchorError,
Wallet,
IdlTypes,
IdlEvents,
} from "@coral-xyz/anchor";
import { Program, BN, AnchorError, Wallet, IdlEvents } from "@coral-xyz/anchor";
import {
PublicKey,
Keypair,
SystemProgram,
SYSVAR_RENT_PUBKEY,
Message,
VersionedTransaction,
} from "@solana/web3.js";
@ -23,7 +14,6 @@ import {
} from "@solana/spl-token";
import { Misc } from "../../target/types/misc";
import { MiscOptional } from "../../target/types/misc_optional";
import { Misc2 } from "../../target/types/misc2";
const utf8 = anchor.utils.bytes.utf8;
const { assert, expect } = require("chai");
@ -38,31 +28,6 @@ const miscTest = (
const provider = anchor.AnchorProvider.env();
const wallet = provider.wallet as Wallet;
anchor.setProvider(provider);
const misc2Program = anchor.workspace.Misc2 as Program<Misc2>;
it("Can allocate extra space for a state constructor", async () => {
// @ts-expect-error
const tx = await program.state.rpc.new();
const addr = await program.state.address();
const state = await program.state.fetch();
const accountInfo = await program.provider.connection.getAccountInfo(
addr
);
assert.isTrue(state.v.equals(Buffer.from([])));
assert.lengthOf(accountInfo.data, 99);
});
it("Can use remaining accounts for a state instruction", async () => {
await program.state.rpc.remainingAccounts({
remainingAccounts: [
{
pubkey: misc2Program.programId,
isWritable: false,
isSigner: false,
},
],
});
});
const data = anchor.web3.Keypair.generate();
@ -148,42 +113,6 @@ const miscTest = (
);
});
it("Can CPI to state instructions", async () => {
const oldData = new anchor.BN(0);
try {
await misc2Program.state.fetch();
// if state account already exists, reset data to oldData.
await program.rpc.testStateCpi(oldData, {
accounts: {
authority: provider.wallet.publicKey,
cpiState: await misc2Program.state.address(),
misc2Program: misc2Program.programId,
},
});
} catch (e) {
// initialize if it doesn't exist
await misc2Program.state.rpc.new({
accounts: {
authority: provider.wallet.publicKey,
},
});
}
let stateAccount = await misc2Program.state.fetch();
assert.isTrue(stateAccount.data.eq(oldData));
assert.isTrue(stateAccount.auth.equals(provider.wallet.publicKey));
const newData = new anchor.BN(2134);
await program.rpc.testStateCpi(newData, {
accounts: {
authority: provider.wallet.publicKey,
cpiState: await misc2Program.state.address(),
misc2Program: misc2Program.programId,
},
});
stateAccount = await misc2Program.state.fetch();
assert.isTrue(stateAccount.data.eq(newData));
assert.isTrue(stateAccount.auth.equals(provider.wallet.publicKey));
});
it("Can retrieve events when simulating a transaction", async () => {
const resp = await program.methods.testSimulate(44).simulate();
const expectedRaw = [

View File

@ -16,7 +16,7 @@ pub mod zero_cpi {
foo: ctx.accounts.foo.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
zero_copy::cpi::update_bar(cpi_ctx, data);
zero_copy::cpi::update_bar(cpi_ctx, data)?;
Ok(())
}
}

View File

@ -2,14 +2,12 @@ import { Idl } from "../../idl.js";
import { BorshInstructionCoder } from "./instruction.js";
import { BorshAccountsCoder } from "./accounts.js";
import { BorshEventCoder } from "./event.js";
import { BorshStateCoder } from "./state.js";
import { BorshTypesCoder } from "./types.js";
import { Coder } from "../index.js";
export { BorshInstructionCoder } from "./instruction.js";
export { BorshAccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js";
export { BorshEventCoder, eventDiscriminator } from "./event.js";
export { BorshStateCoder, stateDiscriminator } from "./state.js";
/**
* BorshCoder is the default Coder for Anchor programs implementing the
@ -28,11 +26,6 @@ export class BorshCoder<A extends string = string, T extends string = string>
*/
readonly accounts: BorshAccountsCoder<A>;
/**
* Coder for state structs.
*/
readonly state: BorshStateCoder;
/**
* Coder for events.
*/
@ -47,9 +40,6 @@ export class BorshCoder<A extends string = string, T extends string = string>
this.instruction = new BorshInstructionCoder(idl);
this.accounts = new BorshAccountsCoder(idl);
this.events = new BorshEventCoder(idl);
if (idl.state) {
this.state = new BorshStateCoder(idl);
}
this.types = new BorshTypesCoder(idl);
}
}

View File

@ -9,7 +9,6 @@ import { AccountMeta, PublicKey } from "@solana/web3.js";
import {
Idl,
IdlField,
IdlStateMethod,
IdlType,
IdlTypeDef,
IdlAccount,
@ -23,10 +22,6 @@ import {
import { IdlCoder } from "./idl.js";
import { InstructionCoder } from "../index.js";
/**
* Namespace for state method function signatures.
*/
export const SIGHASH_STATE_NAMESPACE = "state";
/**
* Namespace for global instruction function signatures (i.e. functions
* that aren't namespaced by the state or any of its trait implementations).
@ -55,16 +50,6 @@ export class BorshInstructionCoder implements InstructionCoder {
});
});
if (idl.state) {
idl.state.methods.map((ix) => {
const sh = sighash(SIGHASH_STATE_NAMESPACE, ix.name);
sighashLayouts.set(bs58.encode(sh), {
layout: this.ixLayout.get(ix.name) as Layout,
name: ix.name,
});
});
}
this.sighashLayouts = sighashLayouts;
}
@ -75,13 +60,6 @@ export class BorshInstructionCoder implements InstructionCoder {
return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
}
/**
* Encodes a program state instruction.
*/
public encodeState(ixName: string, ix: any): Buffer {
return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
}
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const methodName = camelCase(ixName);
@ -95,31 +73,17 @@ export class BorshInstructionCoder implements InstructionCoder {
}
private static parseIxLayout(idl: Idl): Map<string, Layout> {
const stateMethods = idl.state ? idl.state.methods : [];
const ixLayouts = stateMethods
.map((m: IdlStateMethod): [string, Layout<unknown>] => {
let fieldLayouts = m.args.map((arg: IdlField) => {
return IdlCoder.fieldLayout(
arg,
Array.from([...(idl.accounts ?? []), ...(idl.types ?? [])])
);
});
const name = camelCase(m.name);
return [name, borsh.struct(fieldLayouts, name)];
})
.concat(
idl.instructions.map((ix) => {
let fieldLayouts = ix.args.map((arg: IdlField) =>
IdlCoder.fieldLayout(
arg,
Array.from([...(idl.accounts ?? []), ...(idl.types ?? [])])
)
);
const name = camelCase(ix.name);
return [name, borsh.struct(fieldLayouts, name)];
})
const ixLayouts = idl.instructions.map((ix): [string, Layout<unknown>] => {
let fieldLayouts = ix.args.map((arg: IdlField) =>
IdlCoder.fieldLayout(
arg,
Array.from([...(idl.accounts ?? []), ...(idl.types ?? [])])
)
);
const name = camelCase(ix.name);
return [name, borsh.struct(fieldLayouts, name)];
});
return new Map(ixLayouts);
}

View File

@ -1,39 +0,0 @@
import { Buffer } from "buffer";
import { Layout } from "buffer-layout";
import { sha256 } from "js-sha256";
import { Idl } from "../../idl.js";
import { IdlCoder } from "./idl.js";
import * as features from "../../utils/features.js";
export class BorshStateCoder {
private layout: Layout;
public constructor(idl: Idl) {
if (idl.state === undefined) {
throw new Error("Idl state not defined.");
}
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
}
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const len = this.layout.encode(account, buffer);
const disc = await stateDiscriminator(name);
const accData = buffer.slice(0, len);
return Buffer.concat([disc, accData]);
}
public decode<T = any>(ix: Buffer): T {
// Chop off discriminator.
const data = ix.slice(8);
return this.layout.decode(data);
}
}
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
export async function stateDiscriminator(name: string): Promise<Buffer> {
let ns = features.isSet("anchor-deprecated-state") ? "account" : "state";
return Buffer.from(sha256.digest(`${ns}:${name}`)).slice(0, 8);
}

View File

@ -18,11 +18,6 @@ export interface Coder<A extends string = string, T extends string = string> {
*/
readonly accounts: AccountsCoder<A>;
/**
* Coder for state structs.
*/
readonly state: StateCoder;
/**
* Coder for events.
*/
@ -49,7 +44,6 @@ export interface AccountsCoder<A extends string = string> {
export interface InstructionCoder {
encode(ixName: string, ix: any): Buffer;
encodeState(ixName: string, ix: any): Buffer;
}
export interface EventCoder {

View File

@ -1,7 +1,6 @@
import { Idl } from "../../idl.js";
import { Coder } from "../index.js";
import { SystemInstructionCoder } from "./instruction.js";
import { SystemStateCoder } from "./state.js";
import { SystemAccountsCoder } from "./accounts.js";
import { SystemEventsCoder } from "./events.js";
import { SystemTypesCoder } from "./types.js";
@ -12,7 +11,6 @@ import { SystemTypesCoder } from "./types.js";
export class SystemCoder implements Coder {
readonly instruction: SystemInstructionCoder;
readonly accounts: SystemAccountsCoder;
readonly state: SystemStateCoder;
readonly events: SystemEventsCoder;
readonly types: SystemTypesCoder;
@ -20,7 +18,6 @@ export class SystemCoder implements Coder {
this.instruction = new SystemInstructionCoder(idl);
this.accounts = new SystemAccountsCoder(idl);
this.events = new SystemEventsCoder(idl);
this.state = new SystemStateCoder(idl);
this.types = new SystemTypesCoder(idl);
}
}

View File

@ -1,14 +0,0 @@
import { StateCoder } from "../index.js";
import { Idl } from "../../idl";
export class SystemStateCoder implements StateCoder {
// eslint-disable-next-line @typescript-eslint/no-empty-function
constructor(_idl: Idl) {}
encode<T = any>(_name: string, _account: T): Promise<Buffer> {
throw new Error("System does not have state");
}
decode<T = any>(_ix: Buffer): T {
throw new Error("System does not have state");
}
}

View File

@ -368,9 +368,6 @@ export const LangErrorCode = {
AccountReallocExceedsLimit: 3016,
AccountDuplicateReallocs: 3017,
// State.
StateInvalidAddress: 4000,
// Miscellaneous
DeclaredProgramIdMismatch: 4100,
@ -419,7 +416,10 @@ export const LangErrorMessage = new Map([
],
[LangErrorCode.ConstraintSeeds, "A seeds constraint was violated"],
[LangErrorCode.ConstraintExecutable, "An executable constraint was violated"],
[LangErrorCode.ConstraintState, "A state constraint was violated"],
[
LangErrorCode.ConstraintState,
"Deprecated Error, feel free to replace with something else",
],
[LangErrorCode.ConstraintAssociated, "An associated constraint was violated"],
[
LangErrorCode.ConstraintAssociatedInit,
@ -519,12 +519,6 @@ export const LangErrorMessage = new Map([
"The account was duplicated for more than one reallocation",
],
// State.
[
LangErrorCode.StateInvalidAddress,
"The given state account does not have the correct address",
],
// Miscellaneous
[
LangErrorCode.DeclaredProgramIdMismatch,

View File

@ -7,7 +7,6 @@ export type Idl = {
name: string;
docs?: string[];
instructions: IdlInstruction[];
state?: IdlState;
accounts?: IdlAccountDef[];
types?: IdlTypeDef[];
events?: IdlEvent[];
@ -43,11 +42,6 @@ export type IdlInstruction = {
returns?: IdlType;
};
export type IdlState = {
struct: IdlTypeDef;
methods: IdlStateMethod[];
};
export type IdlStateMethod = IdlInstruction;
export type IdlAccountItem = IdlAccount | IdlAccounts;

View File

@ -8,7 +8,6 @@ import NamespaceFactory, {
InstructionNamespace,
TransactionNamespace,
AccountNamespace,
StateClient,
SimulateNamespace,
MethodsNamespace,
ViewNamespace,
@ -207,13 +206,6 @@ export class Program<IDL extends Idl = Idl> {
*/
readonly simulate: SimulateNamespace<IDL>;
/**
* A client for the program state. Similar to the base [[Program]] client,
* one can use this to send transactions and read accounts for the state
* abstraction.
*/
readonly state?: StateClient<IDL>;
/**
* The namespace provides a builder API for all APIs on the program.
* This is an alternative to using namespace the other namespaces..
@ -291,29 +283,20 @@ export class Program<IDL extends Idl = Idl> {
this._events = new EventManager(this._programId, provider, this._coder);
// Dynamic namespaces.
const [
rpc,
instruction,
transaction,
account,
simulate,
methods,
state,
views,
] = NamespaceFactory.build(
idl,
this._coder,
programId,
provider,
getCustomResolver ?? (() => undefined)
);
const [rpc, instruction, transaction, account, simulate, methods, views] =
NamespaceFactory.build(
idl,
this._coder,
programId,
provider,
getCustomResolver ?? (() => undefined)
);
this.rpc = rpc;
this.instruction = instruction;
this.transaction = transaction;
this.account = account;
this.simulate = simulate;
this.methods = methods;
this.state = state;
this.views = views;
}

View File

@ -3,7 +3,6 @@ import { PublicKey } from "@solana/web3.js";
import { Coder } from "../../coder/index.js";
import Provider from "../../provider.js";
import { Idl, IdlInstruction } from "../../idl.js";
import StateFactory, { StateClient } from "./state.js";
import InstructionFactory, { InstructionNamespace } from "./instruction.js";
import TransactionFactory, { TransactionNamespace } from "./transaction.js";
import RpcFactory, { RpcNamespace } from "./rpc.js";
@ -15,7 +14,6 @@ import ViewFactory, { ViewNamespace } from "./views";
import { CustomAccountResolver } from "../accounts-resolver.js";
// Re-exports.
export { StateClient } from "./state.js";
export { InstructionNamespace, InstructionFn } from "./instruction.js";
export { TransactionNamespace, TransactionFn } from "./transaction.js";
export { RpcNamespace, RpcFn } from "./rpc.js";
@ -44,7 +42,6 @@ export default class NamespaceFactory {
AccountNamespace<IDL>,
SimulateNamespace<IDL>,
MethodsNamespace<IDL>,
StateClient<IDL> | undefined,
ViewNamespace<IDL> | undefined
] {
const rpc: RpcNamespace = {};
@ -60,8 +57,6 @@ export default class NamespaceFactory {
? AccountFactory.build(idl, coder, programId, provider)
: ({} as AccountNamespace<IDL>);
const state = StateFactory.build(idl, coder, programId, provider);
idl.instructions.forEach((idlIx) => {
const ixItem = InstructionFactory.build<IDL, typeof idlIx>(
idlIx,
@ -112,7 +107,6 @@ export default class NamespaceFactory {
account,
simulate as SimulateNamespace<IDL>,
methods as MethodsNamespace<IDL>,
state,
view as ViewNamespace<IDL>,
];
}

View File

@ -1,286 +0,0 @@
import EventEmitter from "eventemitter3";
import camelCase from "camelcase";
import {
PublicKey,
SystemProgram,
Commitment,
AccountMeta,
} from "@solana/web3.js";
import Provider, { getProvider } from "../../provider.js";
import { Idl, IdlInstruction, IdlStateMethod, IdlTypeDef } from "../../idl.js";
import { BorshCoder, Coder, stateDiscriminator } from "../../coder/index.js";
import {
RpcNamespace,
InstructionNamespace,
TransactionNamespace,
} from "./index.js";
import { Subscription, validateAccounts, parseIdlErrors } from "../common.js";
import {
findProgramAddressSync,
createWithSeedSync,
} from "../../utils/pubkey.js";
import { Accounts } from "../context.js";
import InstructionNamespaceFactory from "./instruction.js";
import RpcNamespaceFactory from "./rpc.js";
import TransactionNamespaceFactory from "./transaction.js";
import { IdlTypes, TypeDef } from "./types.js";
export default class StateFactory {
public static build<IDL extends Idl>(
idl: IDL,
coder: Coder,
programId: PublicKey,
provider?: Provider
): StateClient<IDL> | undefined {
if (idl.state === undefined) {
return undefined;
}
return new StateClient(idl, programId, provider, coder);
}
}
type NullableMethods<IDL extends Idl> = IDL["state"] extends undefined
? IdlInstruction[]
: NonNullable<IDL["state"]>["methods"];
/**
* A client for the program state. Similar to the base [[Program]] client,
* one can use this to send transactions and read accounts for the state
* abstraction.
*/
export class StateClient<IDL extends Idl> {
/**
* [[RpcNamespace]] for all state methods.
*/
readonly rpc: RpcNamespace<IDL, NullableMethods<IDL>[number]>;
/**
* [[InstructionNamespace]] for all state methods.
*/
readonly instruction: InstructionNamespace<IDL, NullableMethods<IDL>[number]>;
/**
* [[TransactionNamespace]] for all state methods.
*/
readonly transaction: TransactionNamespace<IDL, NullableMethods<IDL>[number]>;
/**
* Returns the program ID owning the state.
*/
get programId(): PublicKey {
return this._programId;
}
private _programId: PublicKey;
private _address: PublicKey;
private _coder: Coder;
private _idl: IDL;
private _sub: Subscription | null;
constructor(
idl: IDL,
programId: PublicKey,
/**
* Returns the client's wallet and network provider.
*/
public readonly provider: Provider = getProvider(),
/**
* Returns the coder.
*/
public readonly coder: Coder = new BorshCoder(idl)
) {
this._idl = idl;
this._programId = programId;
this._address = programStateAddress(programId);
this._sub = null;
// Build namespaces.
const [instruction, transaction, rpc] = ((): [
InstructionNamespace<IDL, NullableMethods<IDL>[number]>,
TransactionNamespace<IDL, NullableMethods<IDL>[number]>,
RpcNamespace<IDL, NullableMethods<IDL>[number]>
] => {
let instruction: InstructionNamespace = {};
let transaction: TransactionNamespace = {};
let rpc: RpcNamespace = {};
idl.state?.methods.forEach(
<I extends NullableMethods<IDL>[number]>(m: I) => {
// Build instruction method.
const ixItem = InstructionNamespaceFactory.build<IDL, I>(
m,
(ixName, ix) => coder.instruction.encodeState(ixName, ix),
programId
);
ixItem["accounts"] = (accounts) => {
const keys = stateInstructionKeys(programId, provider, m, accounts);
return keys.concat(
InstructionNamespaceFactory.accountsArray(
accounts,
m.accounts,
programId,
m.name
)
);
};
// Build transaction method.
const txItem = TransactionNamespaceFactory.build(m, ixItem);
// Build RPC method.
const rpcItem = RpcNamespaceFactory.build(
m,
txItem,
parseIdlErrors(idl),
provider
);
// Attach them all to their respective namespaces.
const name = camelCase(m.name);
instruction[name] = ixItem;
transaction[name] = txItem;
rpc[name] = rpcItem;
}
);
return [
instruction as InstructionNamespace<IDL, NullableMethods<IDL>[number]>,
transaction as TransactionNamespace<IDL, NullableMethods<IDL>[number]>,
rpc as RpcNamespace<IDL, NullableMethods<IDL>[number]>,
];
})();
this.instruction = instruction;
this.transaction = transaction;
this.rpc = rpc;
}
/**
* Returns the deserialized state account.
*/
async fetch(): Promise<
TypeDef<
IDL["state"] extends undefined
? IdlTypeDef
: NonNullable<IDL["state"]>["struct"],
IdlTypes<IDL>
>
> {
const addr = this.address();
const accountInfo = await this.provider.connection.getAccountInfo(addr);
if (accountInfo === null) {
throw new Error(`Account does not exist ${addr.toString()}`);
}
// Assert the account discriminator is correct.
const state = this._idl.state;
if (!state) {
throw new Error("State is not specified in IDL.");
}
const expectedDiscriminator = await stateDiscriminator(state.struct.name);
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
throw new Error("Invalid account discriminator");
}
return this.coder.state.decode(accountInfo.data);
}
/**
* Returns the state address.
*/
address(): PublicKey {
return this._address;
}
/**
* Returns an `EventEmitter` with a `"change"` event that's fired whenever
* the state account cahnges.
*/
subscribe(commitment?: Commitment): EventEmitter {
if (this._sub !== null) {
return this._sub.ee;
}
const ee = new EventEmitter();
const listener = this.provider.connection.onAccountChange(
this.address(),
(acc) => {
const account = this.coder.state.decode(acc.data);
ee.emit("change", account);
},
commitment
);
this._sub = {
ee,
listener,
};
return ee;
}
/**
* Unsubscribes to state changes.
*/
unsubscribe() {
if (this._sub !== null) {
this.provider.connection
.removeAccountChangeListener(this._sub.listener)
.then(async () => {
this._sub = null;
})
.catch(console.error);
}
}
}
// Calculates the deterministic address of the program's "state" account.
function programStateAddress(programId: PublicKey): PublicKey {
let [registrySigner] = findProgramAddressSync([], programId);
return createWithSeedSync(registrySigner, "unversioned", programId);
}
// Returns the common keys that are prepended to all instructions targeting
// the "state" of a program.
function stateInstructionKeys<M extends IdlStateMethod>(
programId: PublicKey,
provider: Provider,
m: M,
accounts: Accounts<M["accounts"][number]>
): AccountMeta[] {
if (m.name === "new") {
// Ctor `new` method.
const [programSigner] = findProgramAddressSync([], programId);
// @ts-expect-error
if (provider.wallet === undefined) {
throw new Error(
"This function requires the Provider interface implementor to have a 'wallet' field."
);
}
return [
{
// @ts-expect-error
pubkey: provider.wallet.publicKey,
isWritable: false,
isSigner: true,
},
{
pubkey: programStateAddress(programId),
isWritable: true,
isSigner: false,
},
{ pubkey: programSigner, isWritable: false, isSigner: false },
{
pubkey: SystemProgram.programId,
isWritable: false,
isSigner: false,
},
{ pubkey: programId, isWritable: false, isSigner: false },
];
} else {
validateAccounts(m.accounts, accounts);
return [
{
pubkey: programStateAddress(programId),
isWritable: true,
isSigner: false,
},
];
}
}