CPI client generation (#19)
This commit is contained in:
parent
830c187279
commit
92b5f74eea
|
@ -67,7 +67,7 @@ pub fn account(
|
|||
};
|
||||
|
||||
proc_macro::TokenStream::from(quote! {
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
#account_strct
|
||||
|
||||
#coder
|
||||
|
|
|
@ -19,9 +19,12 @@ description = "Created with Anchor"
|
|||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "{1}"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
|
||||
[dependencies]
|
||||
borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
|
||||
solana-program = "1.4.3"
|
||||
|
|
|
@ -5,6 +5,8 @@ use anchor_syn::parser::accounts as accounts_parser;
|
|||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
/// Implements an `Accounts` deserializer on the given struct, applying any
|
||||
/// constraints specified via `#[account]` attributes.
|
||||
#[proc_macro_derive(Accounts, attributes(account))]
|
||||
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||
let strct = parse_macro_input!(item as syn::ItemStruct);
|
||||
|
|
|
@ -50,10 +50,11 @@ module.exports = {
|
|||
collapsable: false,
|
||||
title: "Tutorials",
|
||||
children: [
|
||||
"/tutorials/tutorial-0",
|
||||
"/tutorials/tutorial-1",
|
||||
"/tutorials/tutorial-2",
|
||||
],
|
||||
"/tutorials/tutorial-0",
|
||||
"/tutorials/tutorial-1",
|
||||
"/tutorials/tutorial-2",
|
||||
"/tutorials/tutorial-3",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
|
|
|
@ -33,3 +33,9 @@ cd anchor/examples/tutorial/basic-2
|
|||
For now see the [source](https://github.com/project-serum/anchor/tree/master/examples/basic-2).
|
||||
|
||||
TODO
|
||||
|
||||
## Next Steps
|
||||
|
||||
We've covered the basics for writing a single program using Anchor on Solana. But the power of
|
||||
blockchains come not from a single program, but from combining multiple *composable* programs
|
||||
(buzzword alert!). Next, we'll see how to call one program from another.
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# Tutorial 3: Cross Program Invocations (CPI)
|
||||
|
||||
This tutorial covers how to call one program from another, a process known as
|
||||
*cross-program-invocation* (CPI).
|
||||
|
||||
## Clone the Repo
|
||||
|
||||
To get started, clone the repo.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/project-serum/anchor
|
||||
```
|
||||
|
||||
And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-2).
|
||||
|
||||
```bash
|
||||
cd anchor/examples/tutorial/basic-3
|
||||
```
|
||||
|
||||
## Defining a Puppet Program
|
||||
|
||||
We start with the program that will be called by another program, the puppet.
|
||||
|
||||
<<< @/../examples/tutorial/basic-3/programs/puppet/src/lib.rs
|
||||
|
||||
If you've followed along the other tutorials, this should be straight forward. We have
|
||||
a program with two instructions, `initialize`, which does nothing other than the
|
||||
initialization of the account (remember, the program *transparently* prepends a unique 8
|
||||
byte discriminator the first time an account is used). and `set_data`, which takes a previously
|
||||
initialized account, and sets its data field.
|
||||
|
||||
Now, suppose we wanted to call `set_data` from another program.
|
||||
|
||||
## Defining a Puppet Master Program
|
||||
|
||||
We define a new `puppet-master` crate, which successfully executes the Puppet program's `set_data`
|
||||
instruction via CPI.
|
||||
|
||||
<<< @/../examples/tutorial/basic-3/programs/puppet-master/src/lib.rs#core
|
||||
|
||||
Things to notice
|
||||
|
||||
* We create a `CpiContext` object with the target instruction's accounts and program,
|
||||
here `SetData` and `puppet_program`.
|
||||
* To invoke an instruction on another program, just use the `cpi` module on the crate, here, `puppet::cpi::set_data`.
|
||||
* Our `Accounts` struct has a new type, `CpiAccount`, containing the target program's `Puppet`
|
||||
account. Think of `CpiAccount` exactly like `ProgramAccount`, except used for accounts *not*
|
||||
owned by the current program.
|
||||
|
||||
::: details
|
||||
When adding another Anchor program to your crate's `Cargo.toml`, make sure to specify the `no-entrypoint`
|
||||
feature. If you look at the `Cargo.toml` for this example, you'll see
|
||||
`puppet = { path = "../puppet", features = ["no-entrypoint"] }`.
|
||||
:::
|
||||
|
||||
## Signer Seeds
|
||||
|
||||
Often it's useful for a program to sign instructions. For example, if a program controls a token
|
||||
account and wants to send tokens to another account, it must sign. In Solana, this is done by specifying
|
||||
"signer seeds" on CPI (TODO: add link to docs). To do this using the example above, simply change
|
||||
`CpiContext::new(cpi_accounts, cpi_program)` to
|
||||
`CpiContext::new_with_signer(cpi_accounts, cpi_program, signer_seeds)`.
|
||||
|
||||
## Return values
|
||||
|
||||
Solana currently has no way to return values from CPI, alas. However, one can approximate this
|
||||
by having the callee write return values to an account and the caller read that account to
|
||||
retrieve the return value. In future work, Anchor should do this transparently.
|
||||
|
||||
## Conclusion
|
||||
|
||||
That's it for the introductory tutorials for Anchor. For more, see the `examples/` directory in the
|
||||
[repository](https://github.com/project-serum/anchor). For feature requests or bugs, GitHub issues
|
||||
are appreciated. For direct questions or support, join the [Serum Discord](https://discord.com/channels/739225212658122886/752530209848295555).
|
|
@ -11,8 +11,8 @@ mod sysvars {
|
|||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Sysvars {
|
||||
pub clock: Clock,
|
||||
pub rent: Rent,
|
||||
pub stake_history: StakeHistory,
|
||||
pub struct Sysvars<'info> {
|
||||
pub clock: Sysvar<'info, Clock>,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
pub stake_history: Sysvar<'info, StakeHistory>,
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ mod basic_1 {
|
|||
pub struct Initialize<'info> {
|
||||
#[account(init)]
|
||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||
pub rent: Rent,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
|
|
@ -13,7 +13,6 @@ mod basic_2 {
|
|||
let root = &mut ctx.accounts.root;
|
||||
root.authority = authority;
|
||||
root.data = data;
|
||||
root.initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -25,8 +24,7 @@ mod basic_2 {
|
|||
|
||||
pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
|
||||
let leaf = &mut ctx.accounts.leaf;
|
||||
leaf.initialized = true;
|
||||
leaf.root = *ctx.accounts.root.info.key;
|
||||
leaf.root = *ctx.accounts.root.to_account_info().key;
|
||||
leaf.data = data;
|
||||
leaf.custom = custom;
|
||||
Ok(())
|
||||
|
@ -52,7 +50,7 @@ mod basic_2 {
|
|||
pub struct CreateRoot<'info> {
|
||||
#[account(init)]
|
||||
pub root: ProgramAccount<'info, Root>,
|
||||
pub rent: Rent,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -68,7 +66,7 @@ pub struct CreateLeaf<'info> {
|
|||
pub root: ProgramAccount<'info, Root>,
|
||||
#[account(init)]
|
||||
pub leaf: ProgramAccount<'info, Leaf>,
|
||||
pub rent: Rent,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -85,14 +83,12 @@ pub struct UpdateLeaf<'info> {
|
|||
|
||||
#[account]
|
||||
pub struct Root {
|
||||
pub initialized: bool,
|
||||
pub authority: Pubkey,
|
||||
pub data: u64,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Leaf {
|
||||
pub initialized: bool,
|
||||
pub root: Pubkey,
|
||||
pub data: u64,
|
||||
pub custom: MyCustomType,
|
||||
|
@ -100,7 +96,7 @@ pub struct Leaf {
|
|||
|
||||
// Define custom types.
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
pub struct MyCustomType {
|
||||
pub my_data: u64,
|
||||
pub key: Pubkey,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "puppet-master"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "puppet_master"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
|
||||
[dependencies]
|
||||
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||
solana-program = "1.4.3"
|
||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
||||
# anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
|
||||
anchor = { path = "/home/armaniferrante/Documents/code/src/github.com/project-serum/anchor", features = ["derive"] }
|
||||
puppet = { path = "../puppet", features = ["no-entrypoint"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,26 @@
|
|||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
// #region core
|
||||
use anchor::prelude::*;
|
||||
use puppet::{Puppet, SetData};
|
||||
|
||||
#[program]
|
||||
mod puppet_master {
|
||||
use super::*;
|
||||
pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> ProgramResult {
|
||||
let cpi_program = ctx.accounts.puppet_program.clone();
|
||||
let cpi_accounts = SetData {
|
||||
puppet: ctx.accounts.puppet.clone(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
puppet::cpi::set_data(cpi_ctx, data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PullStrings<'info> {
|
||||
#[account(mut)]
|
||||
pub puppet: CpiAccount<'info, Puppet>,
|
||||
pub puppet_program: AccountInfo<'info>,
|
||||
}
|
||||
// #endregion core
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "puppet"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "puppet"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
|
||||
[dependencies]
|
||||
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||
solana-program = "1.4.3"
|
||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
||||
# anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
|
||||
anchor = { path = "/home/armaniferrante/Documents/code/src/github.com/project-serum/anchor", features = ["derive"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,35 @@
|
|||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
use anchor::prelude::*;
|
||||
|
||||
#[program]
|
||||
mod puppet {
|
||||
use super::*;
|
||||
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
|
||||
let puppet = &mut ctx.accounts.puppet;
|
||||
puppet.data = data;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(init)]
|
||||
pub puppet: ProgramAccount<'info, Puppet>,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetData<'info> {
|
||||
#[account(mut)]
|
||||
pub puppet: ProgramAccount<'info, Puppet>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Puppet {
|
||||
pub data: u64,
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
const assert = require("assert");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
|
||||
describe("basic-3", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
it("Performs CPI from puppet master to puppet", async () => {
|
||||
const puppetMaster = anchor.workspace.PuppetMaster;
|
||||
const puppet = anchor.workspace.Puppet;
|
||||
|
||||
// Initialize a new puppet account.
|
||||
const newPuppetAccount = new anchor.web3.Account();
|
||||
const tx = await puppet.rpc.initialize({
|
||||
accounts: {
|
||||
puppet: newPuppetAccount.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [newPuppetAccount],
|
||||
instructions: [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey: newPuppetAccount.publicKey,
|
||||
space: 8 + 8, // Add 8 for the account discriminator.
|
||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(
|
||||
8 + 8
|
||||
),
|
||||
programId: puppet.programId,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Invoke the puppet master to perform a CPI to the puppet.
|
||||
await puppetMaster.rpc.pullStrings(new anchor.BN(111), {
|
||||
accounts: {
|
||||
puppet: newPuppetAccount.publicKey,
|
||||
puppetProgram: puppet.programId,
|
||||
},
|
||||
});
|
||||
|
||||
// Check the state updated.
|
||||
puppetAccount = await puppet.account.puppet(newPuppetAccount.publicKey);
|
||||
assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
|
||||
});
|
||||
});
|
161
src/lib.rs
161
src/lib.rs
|
@ -1,4 +1,5 @@
|
|||
use solana_sdk::account_info::AccountInfo;
|
||||
use solana_sdk::instruction::AccountMeta;
|
||||
use solana_sdk::program_error::ProgramError;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::io::Write;
|
||||
|
@ -8,6 +9,7 @@ pub use anchor_attribute_access_control::access_control;
|
|||
pub use anchor_attribute_account::account;
|
||||
pub use anchor_attribute_program::program;
|
||||
pub use anchor_derive_accounts::Accounts;
|
||||
/// Default serialization format for anchor instructions and accounts.
|
||||
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
||||
|
||||
/// A data structure of Solana accounts that can be deserialized from the input
|
||||
|
@ -16,9 +18,26 @@ pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorS
|
|||
/// (in addition to any done within `AccountDeserialize`) on accounts to ensure
|
||||
/// the accounts maintain any invariants required for the program to run
|
||||
/// securely.
|
||||
pub trait Accounts<'info>: Sized {
|
||||
fn try_accounts(program_id: &Pubkey, from: &[AccountInfo<'info>])
|
||||
-> Result<Self, ProgramError>;
|
||||
pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
|
||||
fn try_accounts(
|
||||
program_id: &Pubkey,
|
||||
from: &mut &[AccountInfo<'info>],
|
||||
) -> Result<Self, ProgramError>;
|
||||
}
|
||||
|
||||
/// Transformation to `AccountMeta` structs.
|
||||
pub trait ToAccountMetas {
|
||||
fn to_account_metas(&self) -> Vec<AccountMeta>;
|
||||
}
|
||||
|
||||
/// Transformation to `AccountInfo` structs.
|
||||
pub trait ToAccountInfos<'info> {
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>>;
|
||||
}
|
||||
|
||||
/// Transformation to an `AccountInfo` struct.
|
||||
pub trait ToAccountInfo<'info> {
|
||||
fn to_account_info(&self) -> AccountInfo<'info>;
|
||||
}
|
||||
|
||||
/// A data structure that can be serialized and stored in an `AccountInfo` data
|
||||
|
@ -48,16 +67,15 @@ pub trait AccountDeserialize: Sized {
|
|||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
||||
}
|
||||
|
||||
/// A container for a deserialized `account` and raw `AccountInfo` object.
|
||||
///
|
||||
/// Using this within a data structure deriving `Accounts` will ensure the
|
||||
/// account is owned by the currently executing program.
|
||||
pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize> {
|
||||
pub info: AccountInfo<'a>,
|
||||
pub account: T,
|
||||
/// Container for a serializable `account`. Use this to reference any account
|
||||
/// owned by the currently executing program.
|
||||
#[derive(Clone)]
|
||||
pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
|
||||
info: AccountInfo<'a>,
|
||||
account: T,
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> {
|
||||
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
|
||||
Self { info, account }
|
||||
}
|
||||
|
@ -93,7 +111,15 @@ impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a, T> {
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
|
||||
for ProgramAccount<'info, T>
|
||||
{
|
||||
fn to_account_info(&self) -> AccountInfo<'info> {
|
||||
self.info.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for ProgramAccount<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -101,30 +127,123 @@ impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a,
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize> DerefMut for ProgramAccount<'a, T> {
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.account
|
||||
}
|
||||
}
|
||||
|
||||
/// A data structure providing non-argument inputs to the Solana program, namely
|
||||
/// the currently executing program's ID and the set of validated, deserialized
|
||||
/// accounts.
|
||||
pub struct Context<'a, 'b, T> {
|
||||
/// Similar to `ProgramAccount`, but to reference any account *not* owned by
|
||||
/// the current program.
|
||||
pub type CpiAccount<'a, T> = ProgramAccount<'a, T>;
|
||||
|
||||
/// Container for a Solana sysvar.
|
||||
pub struct Sysvar<'info, T: solana_sdk::sysvar::Sysvar> {
|
||||
info: AccountInfo<'info>,
|
||||
account: T,
|
||||
}
|
||||
|
||||
impl<'info, T: solana_sdk::sysvar::Sysvar> Sysvar<'info, T> {
|
||||
pub fn from_account_info(
|
||||
acc_info: &AccountInfo<'info>,
|
||||
) -> Result<Sysvar<'info, T>, ProgramError> {
|
||||
Ok(Sysvar {
|
||||
info: acc_info.clone(),
|
||||
account: T::from_account_info(&acc_info)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: solana_sdk::sysvar::Sysvar> Deref for Sysvar<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.account
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: solana_sdk::sysvar::Sysvar> DerefMut for Sysvar<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.account
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfo<'info> for Sysvar<'info, T> {
|
||||
fn to_account_info(&self) -> AccountInfo<'info> {
|
||||
self.info.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> ToAccountInfo<'info> for AccountInfo<'info> {
|
||||
fn to_account_info(&self) -> AccountInfo<'info> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides non-argument inputs to the program.
|
||||
pub struct Context<'a, 'b, 'c, 'info, T> {
|
||||
/// Deserialized accounts.
|
||||
pub accounts: &'a mut T,
|
||||
/// Currently executing program id.
|
||||
pub program_id: &'b Pubkey,
|
||||
/// Remaining accounts given but not deserialized or validated.
|
||||
pub remaining_accounts: &'c [AccountInfo<'info>],
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info, T> Context<'a, 'b, 'c, 'info, T> {
|
||||
pub fn new(
|
||||
accounts: &'a mut T,
|
||||
program_id: &'b Pubkey,
|
||||
remaining_accounts: &'c [AccountInfo<'info>],
|
||||
) -> Self {
|
||||
Self {
|
||||
accounts,
|
||||
program_id,
|
||||
remaining_accounts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Context speciying non-argument inputs for cross-program-invocations.
|
||||
pub struct CpiContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
|
||||
pub accounts: T,
|
||||
pub program: AccountInfo<'info>,
|
||||
pub signer_seeds: &'a [&'b [&'c [u8]]],
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiContext<'a, 'b, 'c, 'info, T> {
|
||||
pub fn new(program: AccountInfo<'info>, accounts: T) -> Self {
|
||||
Self {
|
||||
accounts,
|
||||
program,
|
||||
signer_seeds: &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_signer(
|
||||
accounts: T,
|
||||
program: AccountInfo<'info>,
|
||||
signer_seeds: &'a [&'b [&'c [u8]]],
|
||||
) -> Self {
|
||||
Self {
|
||||
accounts,
|
||||
program,
|
||||
signer_seeds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
|
||||
AnchorDeserialize, AnchorSerialize, Context, ProgramAccount,
|
||||
AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, ProgramAccount,
|
||||
Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
};
|
||||
|
||||
pub use solana_program::msg;
|
||||
pub use solana_sdk::account_info::next_account_info;
|
||||
pub use solana_sdk::account_info::AccountInfo;
|
||||
pub use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||
pub use solana_sdk::entrypoint::ProgramResult;
|
||||
pub use solana_sdk::instruction::AccountMeta;
|
||||
pub use solana_sdk::program_error::ProgramError;
|
||||
pub use solana_sdk::pubkey::Pubkey;
|
||||
pub use solana_sdk::sysvar::clock::Clock;
|
||||
|
@ -137,5 +256,5 @@ pub mod prelude {
|
|||
pub use solana_sdk::sysvar::slot_hashes::SlotHashes;
|
||||
pub use solana_sdk::sysvar::slot_history::SlotHistory;
|
||||
pub use solana_sdk::sysvar::stake_history::StakeHistory;
|
||||
pub use solana_sdk::sysvar::Sysvar;
|
||||
pub use solana_sdk::sysvar::Sysvar as SolanaSysvar;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
use quote::quote;
|
||||
|
||||
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||
// Extract out each account info.
|
||||
let acc_infos: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
|
@ -15,44 +16,49 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (deser_fields, access_checks, return_tys) = {
|
||||
// Deserialization for each field.
|
||||
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(generate_field_deserialization)
|
||||
.collect();
|
||||
// Constraint checks for each account fields.
|
||||
let access_checks: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let checks: Vec<proc_macro2::TokenStream> = f
|
||||
.constraints
|
||||
.iter()
|
||||
.map(|c| generate_constraint(&f, c))
|
||||
.collect();
|
||||
quote! {
|
||||
#(#checks)*
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// Each field in the final deserialized accounts struct.
|
||||
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
#name
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
(deser_fields, access_checks, return_tys)
|
||||
let acc_infos_len = {
|
||||
let acc_infos_len = acc_infos.len();
|
||||
quote! {
|
||||
#acc_infos_len
|
||||
}
|
||||
};
|
||||
|
||||
// Deserialization for each field.
|
||||
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(generate_field_deserialization)
|
||||
.collect();
|
||||
|
||||
// Constraint checks for each account fields.
|
||||
let access_checks: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let checks: Vec<proc_macro2::TokenStream> = f
|
||||
.constraints
|
||||
.iter()
|
||||
.map(|c| generate_constraint(&f, c))
|
||||
.collect();
|
||||
quote! {
|
||||
#(#checks)*
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Each field in the final deserialized accounts struct.
|
||||
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
#name
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Exit program code-blocks for each account.
|
||||
let on_save: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
|
@ -60,16 +66,54 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
let ident = &f.ident;
|
||||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||
_ => return quote! {},
|
||||
};
|
||||
match f.is_mut {
|
||||
false => quote! {},
|
||||
true => quote! {
|
||||
let mut data = self.#info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
self.#ident.account.try_serialize(&mut cursor)?;
|
||||
// Only persist the change if the account is owned by the
|
||||
// current program.
|
||||
if program_id == self.#info.owner {
|
||||
let info = self.#info;
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
self.#ident.try_serialize(&mut cursor)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Implementation for `ToAccountInfos` trait.
|
||||
let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
self.#name.to_account_info()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Implementation for `ToAccountMetas` trait.
|
||||
let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &Field| {
|
||||
let name = &f.ident;
|
||||
let is_signer = match f.is_signer {
|
||||
false => quote! { false },
|
||||
true => quote! { true },
|
||||
};
|
||||
match f.is_mut {
|
||||
false => quote! {
|
||||
AccountMeta::new_readonly(*self.#name.to_account_info().key, #is_signer)
|
||||
},
|
||||
true => quote! {
|
||||
AccountMeta::new(*self.#name.to_account_info().key, #is_signer)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -86,12 +130,15 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
|
||||
quote! {
|
||||
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
||||
fn try_accounts(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||
let acc_infos = &mut accounts.iter();
|
||||
fn try_accounts(program_id: &Pubkey, remaining_accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||
let acc_infos = &mut remaining_accounts.iter();
|
||||
|
||||
// Pull out each account info from the `accounts` slice.
|
||||
#(#acc_infos)*
|
||||
|
||||
// Move the remaining_accounts cursor to the iterator end.
|
||||
*remaining_accounts = &remaining_accounts[#acc_infos_len..];
|
||||
|
||||
// Deserialize each account.
|
||||
#(#deser_fields)*
|
||||
|
||||
|
@ -105,8 +152,24 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
vec![
|
||||
#(#to_acc_infos),*
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl#combined_generics ToAccountMetas for #name#strct_generics {
|
||||
fn to_account_metas(&self) -> Vec<AccountMeta> {
|
||||
vec![
|
||||
#(#to_acc_metas),*
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl#strct_generics #name#strct_generics {
|
||||
pub fn exit(&self) -> ProgramResult {
|
||||
pub fn exit(&self, program_id: &Pubkey) -> ProgramResult {
|
||||
#(#on_save)*
|
||||
Ok(())
|
||||
}
|
||||
|
@ -131,36 +194,47 @@ pub fn generate_field_deserialization(f: &Field) -> proc_macro2::TokenStream {
|
|||
},
|
||||
}
|
||||
}
|
||||
Ty::CpiAccount(acc) => {
|
||||
let account_struct = &acc.account_ident;
|
||||
match f.is_init {
|
||||
false => quote! {
|
||||
let #ident: CpiAccount<#account_struct> = CpiAccount::try_from(#ident)?;
|
||||
},
|
||||
true => quote! {
|
||||
let #ident: CpiAccount<#account_struct> = CpiAccount::try_from_init(#ident)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
Ty::Sysvar(sysvar) => match sysvar {
|
||||
SysvarTy::Clock => quote! {
|
||||
let #ident = Clock::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<Clock> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::Rent => quote! {
|
||||
let #ident = Rent::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<Rent> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::EpochSchedule => quote! {
|
||||
let #ident = EpochSchedule::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<EpochSchedule> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::Fees => quote! {
|
||||
let #ident = Fees::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<Fees> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::RecentBlockHashes => quote! {
|
||||
let #ident = RecentBlockhashes::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<RecentBlockhashes> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::SlotHashes => quote! {
|
||||
let #ident = SlotHashes::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<SlotHashes> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::SlotHistory => quote! {
|
||||
let #ident = SlotHistory::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<SlotHistory> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::StakeHistory => quote! {
|
||||
let #ident = StakeHistory::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<StakeHistory> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::Instructions => quote! {
|
||||
let #ident = Instructions::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<Instructions> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
SysvarTy::Rewards => quote! {
|
||||
let #ident = Rewards::from_account_info(#ident)?;
|
||||
let #ident: Sysvar<Rewards> = Sysvar::from_account_info(#ident)?;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -190,7 +264,7 @@ pub fn generate_constraint_belongs_to(
|
|||
let ident = &f.ident;
|
||||
// todo: would be nice if target could be an account info object.
|
||||
quote! {
|
||||
if &#ident.#target != #target.info.key {
|
||||
if &#ident.#target != #target.to_account_info().key {
|
||||
return Err(ProgramError::Custom(1)); // todo: error codes
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +274,7 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
|
|||
let ident = &f.ident;
|
||||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||
_ => panic!("Invalid syntax: signer cannot be specified."),
|
||||
};
|
||||
quote! {
|
||||
|
@ -223,7 +297,7 @@ pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2:
|
|||
let ident = &f.ident;
|
||||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||
_ => panic!("Invalid syntax: owner cannot be specified."),
|
||||
};
|
||||
match c {
|
||||
|
@ -243,7 +317,7 @@ pub fn generate_constraint_rent_exempt(
|
|||
let ident = &f.ident;
|
||||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||
_ => panic!("Invalid syntax: rent exemption cannot be specified."),
|
||||
};
|
||||
match c {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Program;
|
||||
use crate::{Program, Rpc};
|
||||
use heck::CamelCase;
|
||||
use quote::quote;
|
||||
|
||||
|
@ -8,13 +8,16 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
let dispatch = generate_dispatch(&program);
|
||||
let methods = generate_methods(&program);
|
||||
let instruction = generate_instruction(&program);
|
||||
let cpi = generate_cpi(&program);
|
||||
|
||||
quote! {
|
||||
// Import everything in the mod, in case the user wants to put types
|
||||
// in there.
|
||||
use #mod_name::*;
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
solana_program::entrypoint!(entry);
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
|
||||
let mut data: &[u8] = instruction_data;
|
||||
let ix = instruction::#instruction_name::deserialize(&mut data)
|
||||
|
@ -26,50 +29,30 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
#methods
|
||||
|
||||
#instruction
|
||||
|
||||
#cpi
|
||||
}
|
||||
}
|
||||
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
||||
let program_name = &program.name;
|
||||
let enum_name = instruction_enum_name(program);
|
||||
let dispatch_arms: Vec<proc_macro2::TokenStream> = program
|
||||
.rpcs
|
||||
.iter()
|
||||
.map(|rpc| {
|
||||
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
|
||||
let variant_arm = {
|
||||
let rpc_name_camel = proc_macro2::Ident::new(
|
||||
&rpc.raw_method.sig.ident.to_string().to_camel_case(),
|
||||
rpc.raw_method.sig.ident.span(),
|
||||
);
|
||||
// If no args, output a "unit" variant instead of a struct variant.
|
||||
if rpc.args.len() == 0 {
|
||||
quote! {
|
||||
#enum_name::#rpc_name_camel
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#enum_name::#rpc_name_camel {
|
||||
#(#rpc_arg_names),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let variant_arm = generate_ix_variant(program, rpc);
|
||||
let rpc_name = &rpc.raw_method.sig.ident;
|
||||
let anchor = &rpc.anchor_ident;
|
||||
|
||||
quote! {
|
||||
instruction::#variant_arm => {
|
||||
let mut accounts = #anchor::try_accounts(program_id, accounts)?;
|
||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
|
||||
#program_name::#rpc_name(
|
||||
Context {
|
||||
accounts: &mut accounts,
|
||||
program_id,
|
||||
},
|
||||
Context::new(&mut accounts, program_id, remaining_accounts),
|
||||
#(#rpc_arg_names),*
|
||||
)?;
|
||||
accounts.exit()
|
||||
accounts.exit(program_id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -82,6 +65,26 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
|
||||
let enum_name = instruction_enum_name(program);
|
||||
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
let rpc_name_camel = proc_macro2::Ident::new(
|
||||
&rpc.raw_method.sig.ident.to_string().to_camel_case(),
|
||||
rpc.raw_method.sig.ident.span(),
|
||||
);
|
||||
if rpc.args.len() == 0 {
|
||||
quote! {
|
||||
#enum_name::#rpc_name_camel
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#enum_name::#rpc_name_camel {
|
||||
#(#rpc_arg_names),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
|
||||
let program_mod = &program.program_mod;
|
||||
quote! {
|
||||
|
@ -128,7 +131,56 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
|
|||
|
||||
fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
|
||||
proc_macro2::Ident::new(
|
||||
&program.name.to_string().to_camel_case(),
|
||||
&format!("_{}Instruction", program.name.to_string().to_camel_case()),
|
||||
program.name.span(),
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
|
||||
let cpi_methods: Vec<proc_macro2::TokenStream> = program
|
||||
.rpcs
|
||||
.iter()
|
||||
.map(|rpc| {
|
||||
let accounts_ident = &rpc.anchor_ident;
|
||||
let cpi_method = {
|
||||
let ix_variant = generate_ix_variant(program, rpc);
|
||||
let method_name = &rpc.ident;
|
||||
let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
|
||||
quote! {
|
||||
pub fn #method_name<'a, 'b, 'c, 'info>(
|
||||
ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
|
||||
#(#args),*
|
||||
) -> ProgramResult {
|
||||
let ix = {
|
||||
let ix = instruction::#ix_variant;
|
||||
let data = AnchorSerialize::try_to_vec(&ix)
|
||||
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
let accounts = ctx.accounts.to_account_metas();
|
||||
solana_program::instruction::Instruction {
|
||||
program_id: *ctx.program.key,
|
||||
accounts,
|
||||
data,
|
||||
}
|
||||
};
|
||||
let mut acc_infos = ctx.accounts.to_account_infos();
|
||||
acc_infos.push(ctx.program.clone());
|
||||
solana_sdk::program::invoke_signed(
|
||||
&ix,
|
||||
&acc_infos,
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cpi_method
|
||||
})
|
||||
.collect();
|
||||
quote! {
|
||||
pub mod cpi {
|
||||
use super::*;
|
||||
|
||||
#(#cpi_methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ pub struct Rpc {
|
|||
pub raw_method: syn::ItemFn,
|
||||
pub ident: syn::Ident,
|
||||
pub args: Vec<RpcArg>,
|
||||
// The ident for the struct deriving Accounts.
|
||||
pub anchor_ident: syn::Ident,
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,7 @@ impl AccountsStruct {
|
|||
}
|
||||
}
|
||||
|
||||
// Returns all program owned accounts in the Accounts struct.
|
||||
pub fn account_tys(&self) -> Vec<String> {
|
||||
self.fields
|
||||
.iter()
|
||||
|
@ -73,6 +75,7 @@ pub enum Ty {
|
|||
AccountInfo,
|
||||
ProgramAccount(ProgramAccountTy),
|
||||
Sysvar(SysvarTy),
|
||||
CpiAccount(CpiAccountTy),
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
@ -95,6 +98,12 @@ pub struct ProgramAccountTy {
|
|||
pub account_ident: syn::Ident,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct CpiAccountTy {
|
||||
// The struct type of the account.
|
||||
pub account_ident: syn::Ident,
|
||||
}
|
||||
|
||||
// An access control constraint for an account.
|
||||
pub enum Constraint {
|
||||
Signer(ConstraintSigner),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
|
||||
ConstraintRentExempt, ConstraintSigner, Field, ProgramAccountTy, SysvarTy, Ty,
|
||||
ConstraintRentExempt, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, SysvarTy, Ty,
|
||||
};
|
||||
|
||||
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||
|
@ -68,22 +68,24 @@ fn parse_ty(f: &syn::Field) -> Ty {
|
|||
let segments = &path.segments[0];
|
||||
match segments.ident.to_string().as_str() {
|
||||
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
|
||||
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
|
||||
"Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
|
||||
"AccountInfo" => Ty::AccountInfo,
|
||||
"Clock" => Ty::Sysvar(SysvarTy::Clock),
|
||||
"Rent" => Ty::Sysvar(SysvarTy::Rent),
|
||||
"EpochSchedule" => Ty::Sysvar(SysvarTy::EpochSchedule),
|
||||
"Fees" => Ty::Sysvar(SysvarTy::Fees),
|
||||
"RecentBlockhashes" => Ty::Sysvar(SysvarTy::RecentBlockHashes),
|
||||
"SlotHashes" => Ty::Sysvar(SysvarTy::SlotHashes),
|
||||
"SlotHistory" => Ty::Sysvar(SysvarTy::SlotHistory),
|
||||
"StakeHistory" => Ty::Sysvar(SysvarTy::StakeHistory),
|
||||
"Instructions" => Ty::Sysvar(SysvarTy::Instructions),
|
||||
"Rewards" => Ty::Sysvar(SysvarTy::Rewards),
|
||||
_ => panic!("invalid account type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
|
||||
let account_ident = parse_account(path);
|
||||
CpiAccountTy { account_ident }
|
||||
}
|
||||
|
||||
fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
|
||||
let account_ident = parse_account(path);
|
||||
ProgramAccountTy { account_ident }
|
||||
}
|
||||
|
||||
fn parse_account(path: &syn::Path) -> syn::Ident {
|
||||
let segments = &path.segments[0];
|
||||
let account_ident = match &segments.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => {
|
||||
|
@ -104,7 +106,43 @@ fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
|
|||
}
|
||||
_ => panic!("Invalid ProgramAccount"),
|
||||
};
|
||||
ProgramAccountTy { account_ident }
|
||||
account_ident
|
||||
}
|
||||
|
||||
fn parse_sysvar(path: &syn::Path) -> SysvarTy {
|
||||
let segments = &path.segments[0];
|
||||
let account_ident = match &segments.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => {
|
||||
// Expected: <'info, MyType>.
|
||||
assert!(args.args.len() == 2);
|
||||
match &args.args[1] {
|
||||
syn::GenericArgument::Type(ty) => match ty {
|
||||
syn::Type::Path(ty_path) => {
|
||||
// TODO: allow segmented paths.
|
||||
assert!(ty_path.path.segments.len() == 1);
|
||||
let path_segment = &ty_path.path.segments[0];
|
||||
path_segment.ident.clone()
|
||||
}
|
||||
_ => panic!("Invalid Sysvar"),
|
||||
},
|
||||
_ => panic!("Invalid Sysvar"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Invalid Sysvar"),
|
||||
};
|
||||
match account_ident.to_string().as_str() {
|
||||
"Clock" => SysvarTy::Clock,
|
||||
"Rent" => SysvarTy::Rent,
|
||||
"EpochSchedule" => SysvarTy::EpochSchedule,
|
||||
"Fees" => SysvarTy::Fees,
|
||||
"RecentBlockhashes" => SysvarTy::RecentBlockHashes,
|
||||
"SlotHashes" => SysvarTy::SlotHashes,
|
||||
"SlotHistory" => SysvarTy::SlotHistory,
|
||||
"StakeHistory" => SysvarTy::StakeHistory,
|
||||
"Instructions" => SysvarTy::Instructions,
|
||||
"Rewards" => SysvarTy::Rewards,
|
||||
_ => panic!("Invalid Sysvar"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool, bool) {
|
||||
|
@ -220,19 +258,16 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
|
|||
}
|
||||
|
||||
if !has_owner_constraint {
|
||||
if ty == &Ty::AccountInfo {
|
||||
constraints.push(Constraint::Owner(ConstraintOwner::Skip));
|
||||
} else {
|
||||
if let Ty::ProgramAccount(_) = ty {
|
||||
constraints.push(Constraint::Owner(ConstraintOwner::Program));
|
||||
}
|
||||
}
|
||||
|
||||
match is_rent_exempt {
|
||||
None => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
|
||||
Some(is_re) => match is_re {
|
||||
if let Some(is_re) = is_rent_exempt {
|
||||
match is_re {
|
||||
false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
|
||||
true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
(constraints, is_mut, is_signer, is_init)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@project-serum/anchor",
|
||||
"version": "0.0.0-alpha.2",
|
||||
"version": "0.0.0-alpha.3",
|
||||
"description": "Anchor client",
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
|
|
Loading…
Reference in New Issue