lang: remove the state and interface attributes (#2285)
This commit is contained in:
parent
7236c8bb69
commit
38bbb21c33
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
4
Makefile
4
Makefile
|
@ -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 ../
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
|
@ -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)*
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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" }
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ pub const ERROR_CODE_OFFSET: u32 = 6000;
|
|||
/// - >= 1000 IDL error codes
|
||||
/// - >= 2000 constraint error codes
|
||||
/// - >= 3000 account error codes
|
||||
/// - = 4000 state error code
|
||||
/// - >= 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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)*
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}))
|
||||
}
|
|
@ -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]
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"] }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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 {}
|
|
@ -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" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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,
|
||||
}
|
|
@ -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)));
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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>,
|
||||
}
|
|
@ -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 = [
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue