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! {
|
proc_macro::TokenStream::from(quote! {
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||||
#account_strct
|
#account_strct
|
||||||
|
|
||||||
#coder
|
#coder
|
||||||
|
|
|
@ -19,9 +19,12 @@ description = "Created with Anchor"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
name = "{1}"
|
name = "{1}"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
no-entrypoint = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
|
borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
|
|
|
@ -5,6 +5,8 @@ use anchor_syn::parser::accounts as accounts_parser;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use syn::parse_macro_input;
|
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))]
|
#[proc_macro_derive(Accounts, attributes(account))]
|
||||||
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||||
let strct = parse_macro_input!(item as syn::ItemStruct);
|
let strct = parse_macro_input!(item as syn::ItemStruct);
|
||||||
|
|
|
@ -50,10 +50,11 @@ module.exports = {
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
title: "Tutorials",
|
title: "Tutorials",
|
||||||
children: [
|
children: [
|
||||||
"/tutorials/tutorial-0",
|
"/tutorials/tutorial-0",
|
||||||
"/tutorials/tutorial-1",
|
"/tutorials/tutorial-1",
|
||||||
"/tutorials/tutorial-2",
|
"/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).
|
For now see the [source](https://github.com/project-serum/anchor/tree/master/examples/basic-2).
|
||||||
|
|
||||||
TODO
|
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)]
|
#[derive(Accounts)]
|
||||||
pub struct Sysvars {
|
pub struct Sysvars<'info> {
|
||||||
pub clock: Clock,
|
pub clock: Sysvar<'info, Clock>,
|
||||||
pub rent: Rent,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
pub stake_history: StakeHistory,
|
pub stake_history: Sysvar<'info, StakeHistory>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ mod basic_1 {
|
||||||
pub struct Initialize<'info> {
|
pub struct Initialize<'info> {
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||||
pub rent: Rent,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
|
|
|
@ -13,7 +13,6 @@ mod basic_2 {
|
||||||
let root = &mut ctx.accounts.root;
|
let root = &mut ctx.accounts.root;
|
||||||
root.authority = authority;
|
root.authority = authority;
|
||||||
root.data = data;
|
root.data = data;
|
||||||
root.initialized = true;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +24,7 @@ mod basic_2 {
|
||||||
|
|
||||||
pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
|
pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
|
||||||
let leaf = &mut ctx.accounts.leaf;
|
let leaf = &mut ctx.accounts.leaf;
|
||||||
leaf.initialized = true;
|
leaf.root = *ctx.accounts.root.to_account_info().key;
|
||||||
leaf.root = *ctx.accounts.root.info.key;
|
|
||||||
leaf.data = data;
|
leaf.data = data;
|
||||||
leaf.custom = custom;
|
leaf.custom = custom;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -52,7 +50,7 @@ mod basic_2 {
|
||||||
pub struct CreateRoot<'info> {
|
pub struct CreateRoot<'info> {
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
pub rent: Rent,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
|
@ -68,7 +66,7 @@ pub struct CreateLeaf<'info> {
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub leaf: ProgramAccount<'info, Leaf>,
|
pub leaf: ProgramAccount<'info, Leaf>,
|
||||||
pub rent: Rent,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
|
@ -85,14 +83,12 @@ pub struct UpdateLeaf<'info> {
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
pub initialized: bool,
|
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
pub data: u64,
|
pub data: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Leaf {
|
pub struct Leaf {
|
||||||
pub initialized: bool,
|
|
||||||
pub root: Pubkey,
|
pub root: Pubkey,
|
||||||
pub data: u64,
|
pub data: u64,
|
||||||
pub custom: MyCustomType,
|
pub custom: MyCustomType,
|
||||||
|
@ -100,7 +96,7 @@ pub struct Leaf {
|
||||||
|
|
||||||
// Define custom types.
|
// Define custom types.
|
||||||
|
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||||
pub struct MyCustomType {
|
pub struct MyCustomType {
|
||||||
pub my_data: u64,
|
pub my_data: u64,
|
||||||
pub key: Pubkey,
|
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::account_info::AccountInfo;
|
||||||
|
use solana_sdk::instruction::AccountMeta;
|
||||||
use solana_sdk::program_error::ProgramError;
|
use solana_sdk::program_error::ProgramError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std::io::Write;
|
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_account::account;
|
||||||
pub use anchor_attribute_program::program;
|
pub use anchor_attribute_program::program;
|
||||||
pub use anchor_derive_accounts::Accounts;
|
pub use anchor_derive_accounts::Accounts;
|
||||||
|
/// Default serialization format for anchor instructions and accounts.
|
||||||
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
||||||
|
|
||||||
/// A data structure of Solana accounts that can be deserialized from the input
|
/// 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
|
/// (in addition to any done within `AccountDeserialize`) on accounts to ensure
|
||||||
/// the accounts maintain any invariants required for the program to run
|
/// the accounts maintain any invariants required for the program to run
|
||||||
/// securely.
|
/// securely.
|
||||||
pub trait Accounts<'info>: Sized {
|
pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
|
||||||
fn try_accounts(program_id: &Pubkey, from: &[AccountInfo<'info>])
|
fn try_accounts(
|
||||||
-> Result<Self, ProgramError>;
|
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
|
/// 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>;
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A container for a deserialized `account` and raw `AccountInfo` object.
|
/// Container for a serializable `account`. Use this to reference any account
|
||||||
///
|
/// owned by the currently executing program.
|
||||||
/// Using this within a data structure deriving `Accounts` will ensure the
|
#[derive(Clone)]
|
||||||
/// account is owned by the currently executing program.
|
pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
|
||||||
pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize> {
|
info: AccountInfo<'a>,
|
||||||
pub info: AccountInfo<'a>,
|
account: T,
|
||||||
pub 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> {
|
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
|
||||||
Self { info, account }
|
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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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 {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.account
|
&mut self.account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A data structure providing non-argument inputs to the Solana program, namely
|
/// Similar to `ProgramAccount`, but to reference any account *not* owned by
|
||||||
/// the currently executing program's ID and the set of validated, deserialized
|
/// the current program.
|
||||||
/// accounts.
|
pub type CpiAccount<'a, T> = ProgramAccount<'a, T>;
|
||||||
pub struct Context<'a, 'b, 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,
|
pub accounts: &'a mut T,
|
||||||
|
/// Currently executing program id.
|
||||||
pub program_id: &'b Pubkey,
|
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 mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
|
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_program::msg;
|
||||||
pub use solana_sdk::account_info::next_account_info;
|
pub use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||||
pub use solana_sdk::account_info::AccountInfo;
|
|
||||||
pub use solana_sdk::entrypoint::ProgramResult;
|
pub use solana_sdk::entrypoint::ProgramResult;
|
||||||
|
pub use solana_sdk::instruction::AccountMeta;
|
||||||
pub use solana_sdk::program_error::ProgramError;
|
pub use solana_sdk::program_error::ProgramError;
|
||||||
pub use solana_sdk::pubkey::Pubkey;
|
pub use solana_sdk::pubkey::Pubkey;
|
||||||
pub use solana_sdk::sysvar::clock::Clock;
|
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_hashes::SlotHashes;
|
||||||
pub use solana_sdk::sysvar::slot_history::SlotHistory;
|
pub use solana_sdk::sysvar::slot_history::SlotHistory;
|
||||||
pub use solana_sdk::sysvar::stake_history::StakeHistory;
|
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;
|
use quote::quote;
|
||||||
|
|
||||||
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
|
// Extract out each account info.
|
||||||
let acc_infos: Vec<proc_macro2::TokenStream> = accs
|
let acc_infos: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -15,44 +16,49 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let acc_infos_len = {
|
||||||
let (deser_fields, access_checks, return_tys) = {
|
let acc_infos_len = acc_infos.len();
|
||||||
// Deserialization for each field.
|
quote! {
|
||||||
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
#acc_infos_len
|
||||||
.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)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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
|
let on_save: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -60,16 +66,54 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let ident = &f.ident;
|
let ident = &f.ident;
|
||||||
let info = match f.ty {
|
let info = match f.ty {
|
||||||
Ty::AccountInfo => quote! { #ident },
|
Ty::AccountInfo => quote! { #ident },
|
||||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||||
_ => return quote! {},
|
_ => return quote! {},
|
||||||
};
|
};
|
||||||
match f.is_mut {
|
match f.is_mut {
|
||||||
false => quote! {},
|
false => quote! {},
|
||||||
true => quote! {
|
true => quote! {
|
||||||
let mut data = self.#info.try_borrow_mut_data()?;
|
// Only persist the change if the account is owned by the
|
||||||
let dst: &mut [u8] = &mut data;
|
// current program.
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
if program_id == self.#info.owner {
|
||||||
self.#ident.account.try_serialize(&mut cursor)?;
|
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! {
|
quote! {
|
||||||
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
||||||
fn try_accounts(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
fn try_accounts(program_id: &Pubkey, remaining_accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||||
let acc_infos = &mut accounts.iter();
|
let acc_infos = &mut remaining_accounts.iter();
|
||||||
|
|
||||||
// Pull out each account info from the `accounts` slice.
|
// Pull out each account info from the `accounts` slice.
|
||||||
#(#acc_infos)*
|
#(#acc_infos)*
|
||||||
|
|
||||||
|
// Move the remaining_accounts cursor to the iterator end.
|
||||||
|
*remaining_accounts = &remaining_accounts[#acc_infos_len..];
|
||||||
|
|
||||||
// Deserialize each account.
|
// Deserialize each account.
|
||||||
#(#deser_fields)*
|
#(#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 {
|
impl#strct_generics #name#strct_generics {
|
||||||
pub fn exit(&self) -> ProgramResult {
|
pub fn exit(&self, program_id: &Pubkey) -> ProgramResult {
|
||||||
#(#on_save)*
|
#(#on_save)*
|
||||||
Ok(())
|
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 {
|
Ty::Sysvar(sysvar) => match sysvar {
|
||||||
SysvarTy::Clock => quote! {
|
SysvarTy::Clock => quote! {
|
||||||
let #ident = Clock::from_account_info(#ident)?;
|
let #ident: Sysvar<Clock> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::Rent => quote! {
|
SysvarTy::Rent => quote! {
|
||||||
let #ident = Rent::from_account_info(#ident)?;
|
let #ident: Sysvar<Rent> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::EpochSchedule => quote! {
|
SysvarTy::EpochSchedule => quote! {
|
||||||
let #ident = EpochSchedule::from_account_info(#ident)?;
|
let #ident: Sysvar<EpochSchedule> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::Fees => quote! {
|
SysvarTy::Fees => quote! {
|
||||||
let #ident = Fees::from_account_info(#ident)?;
|
let #ident: Sysvar<Fees> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::RecentBlockHashes => quote! {
|
SysvarTy::RecentBlockHashes => quote! {
|
||||||
let #ident = RecentBlockhashes::from_account_info(#ident)?;
|
let #ident: Sysvar<RecentBlockhashes> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::SlotHashes => quote! {
|
SysvarTy::SlotHashes => quote! {
|
||||||
let #ident = SlotHashes::from_account_info(#ident)?;
|
let #ident: Sysvar<SlotHashes> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::SlotHistory => quote! {
|
SysvarTy::SlotHistory => quote! {
|
||||||
let #ident = SlotHistory::from_account_info(#ident)?;
|
let #ident: Sysvar<SlotHistory> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::StakeHistory => quote! {
|
SysvarTy::StakeHistory => quote! {
|
||||||
let #ident = StakeHistory::from_account_info(#ident)?;
|
let #ident: Sysvar<StakeHistory> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::Instructions => quote! {
|
SysvarTy::Instructions => quote! {
|
||||||
let #ident = Instructions::from_account_info(#ident)?;
|
let #ident: Sysvar<Instructions> = Sysvar::from_account_info(#ident)?;
|
||||||
},
|
},
|
||||||
SysvarTy::Rewards => quote! {
|
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;
|
let ident = &f.ident;
|
||||||
// todo: would be nice if target could be an account info object.
|
// todo: would be nice if target could be an account info object.
|
||||||
quote! {
|
quote! {
|
||||||
if &#ident.#target != #target.info.key {
|
if &#ident.#target != #target.to_account_info().key {
|
||||||
return Err(ProgramError::Custom(1)); // todo: error codes
|
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 ident = &f.ident;
|
||||||
let info = match f.ty {
|
let info = match f.ty {
|
||||||
Ty::AccountInfo => quote! { #ident },
|
Ty::AccountInfo => quote! { #ident },
|
||||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||||
_ => panic!("Invalid syntax: signer cannot be specified."),
|
_ => panic!("Invalid syntax: signer cannot be specified."),
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -223,7 +297,7 @@ pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2:
|
||||||
let ident = &f.ident;
|
let ident = &f.ident;
|
||||||
let info = match f.ty {
|
let info = match f.ty {
|
||||||
Ty::AccountInfo => quote! { #ident },
|
Ty::AccountInfo => quote! { #ident },
|
||||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||||
_ => panic!("Invalid syntax: owner cannot be specified."),
|
_ => panic!("Invalid syntax: owner cannot be specified."),
|
||||||
};
|
};
|
||||||
match c {
|
match c {
|
||||||
|
@ -243,7 +317,7 @@ pub fn generate_constraint_rent_exempt(
|
||||||
let ident = &f.ident;
|
let ident = &f.ident;
|
||||||
let info = match f.ty {
|
let info = match f.ty {
|
||||||
Ty::AccountInfo => quote! { #ident },
|
Ty::AccountInfo => quote! { #ident },
|
||||||
Ty::ProgramAccount(_) => quote! { #ident.info },
|
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||||
_ => panic!("Invalid syntax: rent exemption cannot be specified."),
|
_ => panic!("Invalid syntax: rent exemption cannot be specified."),
|
||||||
};
|
};
|
||||||
match c {
|
match c {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Program;
|
use crate::{Program, Rpc};
|
||||||
use heck::CamelCase;
|
use heck::CamelCase;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
|
@ -8,13 +8,16 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
||||||
let dispatch = generate_dispatch(&program);
|
let dispatch = generate_dispatch(&program);
|
||||||
let methods = generate_methods(&program);
|
let methods = generate_methods(&program);
|
||||||
let instruction = generate_instruction(&program);
|
let instruction = generate_instruction(&program);
|
||||||
|
let cpi = generate_cpi(&program);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
// Import everything in the mod, in case the user wants to put types
|
// Import everything in the mod, in case the user wants to put types
|
||||||
// in there.
|
// in there.
|
||||||
use #mod_name::*;
|
use #mod_name::*;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
solana_program::entrypoint!(entry);
|
solana_program::entrypoint!(entry);
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
|
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
|
||||||
let mut data: &[u8] = instruction_data;
|
let mut data: &[u8] = instruction_data;
|
||||||
let ix = instruction::#instruction_name::deserialize(&mut data)
|
let ix = instruction::#instruction_name::deserialize(&mut data)
|
||||||
|
@ -26,50 +29,30 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
||||||
#methods
|
#methods
|
||||||
|
|
||||||
#instruction
|
#instruction
|
||||||
|
|
||||||
|
#cpi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let program_name = &program.name;
|
let program_name = &program.name;
|
||||||
let enum_name = instruction_enum_name(program);
|
|
||||||
let dispatch_arms: Vec<proc_macro2::TokenStream> = program
|
let dispatch_arms: Vec<proc_macro2::TokenStream> = program
|
||||||
.rpcs
|
.rpcs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|rpc| {
|
.map(|rpc| {
|
||||||
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
||||||
|
let variant_arm = generate_ix_variant(program, rpc);
|
||||||
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 rpc_name = &rpc.raw_method.sig.ident;
|
let rpc_name = &rpc.raw_method.sig.ident;
|
||||||
let anchor = &rpc.anchor_ident;
|
let anchor = &rpc.anchor_ident;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
instruction::#variant_arm => {
|
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(
|
#program_name::#rpc_name(
|
||||||
Context {
|
Context::new(&mut accounts, program_id, remaining_accounts),
|
||||||
accounts: &mut accounts,
|
|
||||||
program_id,
|
|
||||||
},
|
|
||||||
#(#rpc_arg_names),*
|
#(#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 {
|
pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let program_mod = &program.program_mod;
|
let program_mod = &program.program_mod;
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -128,7 +131,56 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
|
||||||
|
|
||||||
fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
|
fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
|
||||||
proc_macro2::Ident::new(
|
proc_macro2::Ident::new(
|
||||||
&program.name.to_string().to_camel_case(),
|
&format!("_{}Instruction", program.name.to_string().to_camel_case()),
|
||||||
program.name.span(),
|
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 raw_method: syn::ItemFn,
|
||||||
pub ident: syn::Ident,
|
pub ident: syn::Ident,
|
||||||
pub args: Vec<RpcArg>,
|
pub args: Vec<RpcArg>,
|
||||||
|
// The ident for the struct deriving Accounts.
|
||||||
pub anchor_ident: syn::Ident,
|
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> {
|
pub fn account_tys(&self) -> Vec<String> {
|
||||||
self.fields
|
self.fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -73,6 +75,7 @@ pub enum Ty {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
ProgramAccount(ProgramAccountTy),
|
ProgramAccount(ProgramAccountTy),
|
||||||
Sysvar(SysvarTy),
|
Sysvar(SysvarTy),
|
||||||
|
CpiAccount(CpiAccountTy),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -95,6 +98,12 @@ pub struct ProgramAccountTy {
|
||||||
pub account_ident: syn::Ident,
|
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.
|
// An access control constraint for an account.
|
||||||
pub enum Constraint {
|
pub enum Constraint {
|
||||||
Signer(ConstraintSigner),
|
Signer(ConstraintSigner),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
|
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 {
|
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
|
@ -68,22 +68,24 @@ fn parse_ty(f: &syn::Field) -> Ty {
|
||||||
let segments = &path.segments[0];
|
let segments = &path.segments[0];
|
||||||
match segments.ident.to_string().as_str() {
|
match segments.ident.to_string().as_str() {
|
||||||
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
|
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
|
||||||
|
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
|
||||||
|
"Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
|
||||||
"AccountInfo" => Ty::AccountInfo,
|
"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"),
|
_ => 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 {
|
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 segments = &path.segments[0];
|
||||||
let account_ident = match &segments.arguments {
|
let account_ident = match &segments.arguments {
|
||||||
syn::PathArguments::AngleBracketed(args) => {
|
syn::PathArguments::AngleBracketed(args) => {
|
||||||
|
@ -104,7 +106,43 @@ fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid ProgramAccount"),
|
_ => 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) {
|
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 !has_owner_constraint {
|
||||||
if ty == &Ty::AccountInfo {
|
if let Ty::ProgramAccount(_) = ty {
|
||||||
constraints.push(Constraint::Owner(ConstraintOwner::Skip));
|
|
||||||
} else {
|
|
||||||
constraints.push(Constraint::Owner(ConstraintOwner::Program));
|
constraints.push(Constraint::Owner(ConstraintOwner::Program));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match is_rent_exempt {
|
if let Some(is_re) = is_rent_exempt {
|
||||||
None => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
|
match is_re {
|
||||||
Some(is_re) => match is_re {
|
|
||||||
false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
|
false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
|
||||||
true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
|
true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(constraints, is_mut, is_signer, is_init)
|
(constraints, is_mut, is_signer, is_init)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@project-serum/anchor",
|
"name": "@project-serum/anchor",
|
||||||
"version": "0.0.0-alpha.2",
|
"version": "0.0.0-alpha.3",
|
||||||
"description": "Anchor client",
|
"description": "Anchor client",
|
||||||
"main": "dist/cjs/index.js",
|
"main": "dist/cjs/index.js",
|
||||||
"module": "dist/esm/index.js",
|
"module": "dist/esm/index.js",
|
||||||
|
|
Loading…
Reference in New Issue