Composable Accounts derivations (#21)

This commit is contained in:
Armani Ferrante 2021-01-14 22:35:50 -08:00 committed by GitHub
parent 20bd3c2db8
commit 34a3474663
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1075 additions and 452 deletions

View File

@ -41,6 +41,7 @@ jobs:
name: Runs the examples
script:
- pushd examples/sysvars && anchor test && popd
- pushd examples/composite && anchor test && popd
- pushd examples/tutorial/basic-0 && anchor test && popd
- pushd examples/tutorial/basic-1 && anchor test && popd
- pushd examples/tutorial/basic-2 && anchor test && popd

28
Cargo.lock generated
View File

@ -43,20 +43,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "anchor"
version = "0.1.0"
dependencies = [
"anchor-attribute-access-control",
"anchor-attribute-account",
"anchor-attribute-program",
"anchor-derive-accounts",
"borsh",
"solana-program",
"solana-sdk",
"thiserror",
]
[[package]]
name = "anchor-attribute-access-control"
version = "0.1.0"
@ -121,6 +107,20 @@ dependencies = [
"syn 1.0.54",
]
[[package]]
name = "anchor-lang"
version = "0.1.0"
dependencies = [
"anchor-attribute-access-control",
"anchor-attribute-account",
"anchor-attribute-program",
"anchor-derive-accounts",
"borsh",
"solana-program",
"solana-sdk",
"thiserror",
]
[[package]]
name = "anchor-syn"
version = "0.1.0"

View File

@ -1,5 +1,5 @@
[package]
name = "anchor"
name = "anchor-lang"
version = "0.1.0"
description = ""
repository = "https://github.com/project-serum/serum-dex"

View File

@ -55,7 +55,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)]
@ -95,7 +95,7 @@ purposes of the Accounts macro) that can be specified on a struct deriving `Acco
| `#[account(mut)]` | On `ProgramAccount` structs. | Marks the account as mutable and persists the state transition. |
| `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. |
| `#[account(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
| `#[account(owner = program \| skip)]` | On `ProgramAccount` and `AccountInfo` structs | Checks the owner of the account is the current program or skips the check. |
| `#[account(owner = program \| skip)]` | On `AccountInfo` structs | Checks the owner of the account is the current program or skips the check. |
| `#[account("<literal>")]` | On `ProgramAccount` structs | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
| `#[account(rent_exempt = <skip>)]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt. Similarly, omitting `= skip` will mark the account rent exempt. |

View File

@ -17,7 +17,7 @@ pub fn account(
let discriminator_preimage = format!("account:{}", account_name.to_string());
let coder = quote! {
impl anchor::AccountSerialize for #account_name {
impl anchor_lang::AccountSerialize for #account_name {
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
// TODO: we shouldn't have to hash at runtime. However, rust
// is not happy when trying to include solana-sdk from
@ -39,7 +39,7 @@ pub fn account(
}
}
impl anchor::AccountDeserialize for #account_name {
impl anchor_lang::AccountDeserialize for #account_name {
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
let mut discriminator = [0u8; 8];
discriminator.copy_from_slice(

View File

@ -30,7 +30,7 @@ cpi = ["no-entrypoint"]
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-lang = {{ git = "https://github.com/project-serum/anchor", features = ["derive"] }}
"#,
name,
name.to_snake_case(),
@ -47,7 +47,7 @@ pub fn lib_rs(name: &str) -> String {
format!(
r#"#![feature(proc_macro_hygiene)]
use anchor::prelude::*;
use anchor_lang::prelude::*;
#[program]
mod {} {{

View File

@ -0,0 +1,2 @@
cluster = "localnet"
wallet = "/home/armaniferrante/.config/solana/id.json"

View File

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

View File

@ -0,0 +1,19 @@
[package]
name = "composite"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "composite"
[features]
no-entrypoint = []
cpi = ["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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

View File

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

View File

@ -0,0 +1,65 @@
//! This example demonstrates the ability to compose together multiple
//! structs deriving `Accounts`. See `CompositeUpdate`, below.
#![feature(proc_macro_hygiene)]
use anchor_lang::prelude::*;
#[program]
mod composite {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
Ok(())
}
pub fn composite_update(
ctx: Context<CompositeUpdate>,
dummy_a: u64,
dummy_b: String,
) -> ProgramResult {
let a = &mut ctx.accounts.foo.dummy_a;
let b = &mut ctx.accounts.bar.dummy_b;
a.data = dummy_a;
b.data = dummy_b;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init)]
pub dummy_a: ProgramAccount<'info, DummyA>,
#[account(init)]
pub dummy_b: ProgramAccount<'info, DummyB>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct CompositeUpdate<'info> {
foo: Foo<'info>,
bar: Bar<'info>,
}
#[derive(Accounts)]
pub struct Foo<'info> {
#[account(mut)]
pub dummy_a: ProgramAccount<'info, DummyA>,
}
#[derive(Accounts)]
pub struct Bar<'info> {
#[account(mut)]
pub dummy_b: ProgramAccount<'info, DummyB>,
}
#[account]
pub struct DummyA {
pub data: u64,
}
#[account]
pub struct DummyB {
pub data: String,
}

View File

@ -0,0 +1,59 @@
const assert = require('assert');
const anchor = require('@project-serum/anchor');
describe('composite', () => {
const provider = anchor.Provider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);
it('Is initialized!', async () => {
const program = anchor.workspace.Composite;
const dummyA = new anchor.web3.Account();
const dummyB = new anchor.web3.Account();
const tx = await program.rpc.initialize({
accounts: {
dummyA: dummyA.publicKey,
dummyB: dummyB.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [dummyA, dummyB],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: dummyA.publicKey,
space: 8 + 8,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 8
),
programId: program.programId,
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: dummyB.publicKey,
space: 8 + 100,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 100
),
programId: program.programId,
}),
],
});
await program.rpc.compositeUpdate(new anchor.BN(1234), 'hello', {
accounts: {
dummyA: dummyA.publicKey,
dummyB: dummyB.publicKey,
},
});
const dummyAAccount = await program.account.dummyA(dummyA.publicKey);
const dummyBAccount = await program.account.dummyB(dummyB.publicKey);
assert.ok(dummyAAccount.data.eq(new anchor.BN(1234)));
assert.ok(dummyBAccount.data === 'hello');
});
});

View File

@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

View File

@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

View File

@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

View File

@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

View File

@ -1,44 +1,49 @@
#![feature(proc_macro_hygiene)]
use anchor::prelude::*;
use anchor_lang::prelude::*;
// Define the program's RPC handlers.
// Define the program's instruction handlers.
#[program]
mod basic_2 {
use super::*;
#[access_control(not_zero(authority))]
pub fn create_root(ctx: Context<CreateRoot>, authority: Pubkey, data: u64) -> ProgramResult {
let root = &mut ctx.accounts.root;
root.authority = authority;
root.data = data;
Ok(())
}
pub fn update_root(ctx: Context<UpdateRoot>, data: u64) -> ProgramResult {
let root = &mut ctx.accounts.root;
root.data = data;
Ok(())
}
pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
let leaf = &mut ctx.accounts.leaf;
leaf.root = *ctx.accounts.root.to_account_info().key;
leaf.data = data;
leaf.custom = custom;
Ok(())
}
pub fn update_leaf(
ctx: Context<UpdateLeaf>,
data: u64,
custom: Option<MyCustomType>,
pub fn create_author(
ctx: Context<CreateAuthor>,
authority: Pubkey,
name: String,
) -> ProgramResult {
let leaf = &mut ctx.accounts.leaf;
leaf.data = data;
if let Some(custom) = custom {
leaf.custom = custom;
let author = &mut ctx.accounts.author;
author.authority = authority;
author.name = name;
Ok(())
}
pub fn update_author(ctx: Context<UpdateAuthor>, name: String) -> ProgramResult {
let author = &mut ctx.accounts.author;
author.name = name;
Ok(())
}
pub fn create_book(ctx: Context<CreateBook>, title: String, pages: Vec<Page>) -> ProgramResult {
let book = &mut ctx.accounts.book;
book.author = *ctx.accounts.author.to_account_info().key;
book.title = title;
book.pages = pages;
Ok(())
}
pub fn update_book(
ctx: Context<UpdateBook>,
title: Option<String>,
pages: Option<Vec<Page>>,
) -> ProgramResult {
let book = &mut ctx.accounts.book;
if let Some(title) = title {
book.title = title;
}
if let Some(pages) = pages {
book.pages = pages;
}
Ok(())
}
@ -47,66 +52,60 @@ mod basic_2 {
// Define the validated accounts for each handler.
#[derive(Accounts)]
pub struct CreateRoot<'info> {
pub struct CreateAuthor<'info> {
#[account(init)]
pub root: ProgramAccount<'info, Root>,
pub author: ProgramAccount<'info, Author>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct UpdateRoot<'info> {
pub struct UpdateAuthor<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account(mut, "&root.authority == authority.key")]
pub root: ProgramAccount<'info, Root>,
#[account(mut, "&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
}
#[derive(Accounts)]
pub struct CreateLeaf<'info> {
pub root: ProgramAccount<'info, Root>,
pub struct CreateBook<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account("&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
#[account(init)]
pub leaf: ProgramAccount<'info, Leaf>,
pub book: ProgramAccount<'info, Book>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct UpdateLeaf<'info> {
pub struct UpdateBook<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account("&root.authority == authority.key")]
pub root: ProgramAccount<'info, Root>,
#[account(mut, belongs_to = root)]
pub leaf: ProgramAccount<'info, Leaf>,
#[account("&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
#[account(mut, belongs_to = author)]
pub book: ProgramAccount<'info, Book>,
}
// Define the program owned accounts.
#[account]
pub struct Root {
pub struct Author {
pub authority: Pubkey,
pub data: u64,
pub name: String,
}
#[account]
pub struct Leaf {
pub root: Pubkey,
pub data: u64,
pub custom: MyCustomType,
pub struct Book {
pub author: Pubkey,
pub title: String,
pub pages: Vec<Page>,
}
// Define custom types.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct MyCustomType {
pub my_data: u64,
pub key: Pubkey,
}
// Define any auxiliary access control checks.
fn not_zero(authority: Pubkey) -> ProgramResult {
if authority == Pubkey::new_from_array([0; 32]) {
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
pub struct Page {
pub content: String,
pub footnote: String,
}

View File

@ -1,11 +1,114 @@
const anchor = require('@project-serum/anchor');
const assert = require("assert");
//const anchor = require('@project-serum/anchor');
const anchor = require("/home/armaniferrante/Documents/code/src/github.com/project-serum/anchor/ts");
describe('basic-2', () => {
describe("basic-2", () => {
const provider = anchor.Provider.local();
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(provider);
it('Applies constraints and access control', async () => {
const program = anchor.workspace.Basic2;
// Author for the tests.
const author = new anchor.web3.Account();
// Program for the tests.
const program = anchor.workspace.Basic2;
it("Creates an author", async () => {
await program.rpc.createAuthor(provider.wallet.publicKey, "Ghost", {
accounts: {
author: author.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [author],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: author.publicKey,
space: 8 + 1000,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 1000
),
programId: program.programId,
}),
],
});
let authorAccount = await program.account.author(author.publicKey);
assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
assert.ok(authorAccount.name === "Ghost");
});
it("Updates an author", async () => {
await program.rpc.updateAuthor("Updated author", {
accounts: {
author: author.publicKey,
authority: provider.wallet.publicKey,
},
});
authorAccount = await program.account.author(author.publicKey);
assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
assert.ok(authorAccount.name === "Updated author");
});
// Book params to use accross tests.
const book = new anchor.web3.Account();
const pages = [
{
content: "first page",
footnote: "first footnote",
},
{
content: "second page",
footnote: "second footnote",
},
];
it("Creates a book", async () => {
await program.rpc.createBook("Book title", pages, {
accounts: {
authority: provider.wallet.publicKey,
author: author.publicKey,
book: book.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [book],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: book.publicKey,
space: 8 + 1000,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 1000
),
programId: program.programId,
}),
],
});
const bookAccount = await program.account.book(book.publicKey);
assert.ok(bookAccount.author.equals(author.publicKey));
assert.ok(bookAccount.title === "Book title");
assert.deepEqual(bookAccount.pages, pages);
});
it("Updates a book", async () => {
await program.rpc.updateBook("New book title", null, {
accounts: {
authority: provider.wallet.publicKey,
author: author.publicKey,
book: book.publicKey,
},
});
const bookAccount = await program.account.book(book.publicKey);
assert.ok(bookAccount.author.equals(author.publicKey));
assert.ok(bookAccount.title === "New book title");
assert.deepEqual(bookAccount.pages, pages);
});
});

View File

@ -16,5 +16,5 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
puppet = { path = "../puppet", features = ["cpi"] }

View File

@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
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-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

41
src/account_info.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::{Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_sdk::account_info::AccountInfo;
use solana_sdk::instruction::AccountMeta;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey;
impl<'info> Accounts<'info> for AccountInfo<'info> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.len() == 0 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let account = &accounts[0];
*accounts = &accounts[1..];
Ok(account.clone())
}
}
impl<'info> ToAccountMetas for AccountInfo<'info> {
fn to_account_metas(&self) -> Vec<AccountMeta> {
let meta = match self.is_writable {
false => AccountMeta::new_readonly(*self.key, self.is_signer),
true => AccountMeta::new(*self.key, self.is_signer),
};
vec![meta]
}
}
impl<'info> ToAccountInfos<'info> for AccountInfo<'info> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.clone()]
}
}
impl<'info> ToAccountInfo<'info> for AccountInfo<'info> {
fn to_account_info(&self) -> AccountInfo<'info> {
self.clone()
}
}

56
src/context.rs Normal file
View File

@ -0,0 +1,56 @@
use crate::Accounts;
use solana_sdk::account_info::AccountInfo;
use solana_sdk::pubkey::Pubkey;
/// Provides non-argument inputs to the program.
pub struct Context<'a, 'b, 'c, 'info, T> {
/// Currently executing program id.
pub program_id: &'a Pubkey,
/// Deserialized accounts.
pub accounts: &'b mut T,
/// 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(
program_id: &'a Pubkey,
accounts: &'b mut T,
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,
}
}
}

90
src/cpi_account.rs Normal file
View File

@ -0,0 +1,90 @@
use crate::{
AccountDeserialize, AccountSerialize, Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
use solana_sdk::account_info::AccountInfo;
use solana_sdk::instruction::AccountMeta;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey;
use std::ops::{Deref, DerefMut};
/// Container for any account *not* owned by the current program.
#[derive(Clone)]
pub struct CpiAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
info: AccountInfo<'a>,
account: T,
}
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> CpiAccount<'a, T> {
pub fn new(info: AccountInfo<'a>, account: T) -> CpiAccount<'a, T> {
Self { info, account }
}
/// Deserializes the given `info` into a `CpiAccount`.
pub fn try_from(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(CpiAccount::new(
info.clone(),
T::try_deserialize(&mut data)?,
))
}
}
impl<'info, T> Accounts<'info> for CpiAccount<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.len() == 0 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let account = &accounts[0];
*accounts = &accounts[1..];
let pa = CpiAccount::try_from(account)?;
Ok(pa)
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for CpiAccount<'info, T>
{
fn to_account_metas(&self) -> Vec<AccountMeta> {
let meta = match self.info.is_writable {
false => AccountMeta::new_readonly(*self.info.key, self.info.is_signer),
true => AccountMeta::new(*self.info.key, self.info.is_signer),
};
vec![meta]
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
for CpiAccount<'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
for CpiAccount<'info, T>
{
fn to_account_info(&self) -> AccountInfo<'info> {
self.info.clone()
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for CpiAccount<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.account
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for CpiAccount<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.account
}
}

View File

@ -1,10 +1,42 @@
//! Anchor ⚓ is a framework for Solana's Sealevel runtime providing several
//! convenient developer tools.
//!
//! - Rust eDSL for writing safe, secure, and high level Solana programs
//! - [IDL](https://en.wikipedia.org/wiki/Interface_description_language) specification
//! - TypeScript package for generating clients from IDL
//! - CLI and workspace management for developing complete applications
//!
//! If you're familiar with developing in Ethereum's
//! [Solidity](https://docs.soliditylang.org/en/v0.7.4/),
//! [Truffle](https://www.trufflesuite.com/),
//! [web3.js](https://github.com/ethereum/web3.js) or Parity's
//! [Ink!](https://github.com/paritytech/ink), then the experience will be
//! familiar. Although the syntax and semantics are targeted at Solana, the high
//! level workflow of writing RPC request handlers, emitting an IDL, and
//! generating clients from IDL is the same.
//!
//! For detailed tutorials and examples on how to use Anchor, see the guided
//! [tutorials](https://project-serum.github.io/anchor) or examples in the GitHub
//! [repository](https://github.com/project-serum/anchor).
//!
//! Presented here are the Rust primitives for building on Solana.
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;
use std::ops::{Deref, DerefMut};
mod account_info;
mod context;
mod cpi_account;
mod program_account;
mod sysvar;
pub use crate::context::{Context, CpiContext};
pub use crate::cpi_account::CpiAccount;
pub use crate::program_account::ProgramAccount;
pub use crate::sysvar::Sysvar;
pub use anchor_attribute_access_control::access_control;
pub use anchor_attribute_account::account;
pub use anchor_attribute_program::program;
@ -12,7 +44,7 @@ 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
/// A data structure of accounts that can be deserialized from the input
/// of a Solana program. Due to the freewheeling nature of the accounts array,
/// implementations of this trait should perform any and all constraint checks
/// (in addition to any done within `AccountDeserialize`) on accounts to ensure
@ -21,7 +53,18 @@ pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorS
pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
fn try_accounts(
program_id: &Pubkey,
from: &mut &[AccountInfo<'info>],
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError>;
}
/// A data structure of accounts providing a one time deserialization upon
/// initialization, i.e., when the data array for a given account is zeroed.
/// For all subsequent deserializations, it's expected that
/// [Accounts](trait.Accounts.html) is used.
pub trait AccountsInit<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
fn try_accounts_init(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError>;
}
@ -67,177 +110,13 @@ pub trait AccountDeserialize: Sized {
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
}
/// 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 + Clone> ProgramAccount<'a, T> {
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
Self { info, account }
}
/// Deserializes the given `info` into a `ProgramAccount`.
pub fn try_from(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(ProgramAccount::new(
info.clone(),
T::try_deserialize(&mut data)?,
))
}
/// Deserializes the zero-initialized `info` into a `ProgramAccount` without
/// checking the account type. This should only be used upon program account
/// initialization (since the entire account data array is zeroed and thus
/// no account type is set).
pub fn try_from_init(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
// The discriminator should be zero, since we're initializing.
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
}
Ok(ProgramAccount::new(
info.clone(),
T::try_deserialize_unchecked(&mut data)?,
))
}
}
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 {
&self.account
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.account
}
}
/// 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,
}
}
}
/// The prelude contains all commonly used components of the crate.
/// All programs should include it via `anchor_lang::prelude::*;`.
pub mod prelude {
pub use super::{
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, ProgramAccount,
Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
AccountsInit, AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext,
ProgramAccount, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
pub use solana_program::msg;

131
src/program_account.rs Normal file
View File

@ -0,0 +1,131 @@
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsInit, ToAccountInfo, ToAccountInfos,
ToAccountMetas,
};
use solana_sdk::account_info::AccountInfo;
use solana_sdk::instruction::AccountMeta;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey;
use std::ops::{Deref, DerefMut};
/// 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 + Clone> ProgramAccount<'a, T> {
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
Self { info, account }
}
/// Deserializes the given `info` into a `ProgramAccount`.
pub fn try_from(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(ProgramAccount::new(
info.clone(),
T::try_deserialize(&mut data)?,
))
}
/// Deserializes the zero-initialized `info` into a `ProgramAccount` without
/// checking the account type. This should only be used upon program account
/// initialization (since the entire account data array is zeroed and thus
/// no account type is set).
pub fn try_from_init(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
// The discriminator should be zero, since we're initializing.
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
}
Ok(ProgramAccount::new(
info.clone(),
T::try_deserialize_unchecked(&mut data)?,
))
}
}
impl<'info, T> Accounts<'info> for ProgramAccount<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.len() == 0 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let account = &accounts[0];
*accounts = &accounts[1..];
let pa = ProgramAccount::try_from(account)?;
if pa.info.owner != program_id {}
Ok(pa)
}
}
impl<'info, T> AccountsInit<'info> for ProgramAccount<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
fn try_accounts_init(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.len() == 0 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let account = &accounts[0];
*accounts = &accounts[1..];
ProgramAccount::try_from_init(account)
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for ProgramAccount<'info, T>
{
fn to_account_metas(&self) -> Vec<AccountMeta> {
let meta = match self.info.is_writable {
false => AccountMeta::new_readonly(*self.info.key, self.info.is_signer),
true => AccountMeta::new(*self.info.key, self.info.is_signer),
};
vec![meta]
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
for ProgramAccount<'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}
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 {
&self.account
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.account
}
}

69
src/sysvar.rs Normal file
View File

@ -0,0 +1,69 @@
use crate::{Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_sdk::account_info::AccountInfo;
use solana_sdk::instruction::AccountMeta;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey;
use std::ops::{Deref, DerefMut};
/// Container for sysvars.
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<'info, T: solana_sdk::sysvar::Sysvar> Accounts<'info> for Sysvar<'info, T> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.len() == 0 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let account = &accounts[0];
*accounts = &accounts[1..];
Sysvar::from_account_info(account)
}
}
impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountMetas for Sysvar<'info, T> {
fn to_account_metas(&self) -> Vec<AccountMeta> {
vec![AccountMeta::new_readonly(*self.info.key, false)]
}
}
impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfos<'info> for Sysvar<'info, T> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}
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()
}
}

View File

@ -1,39 +1,44 @@
use crate::{
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
ConstraintRentExempt, ConstraintSigner, Field, SysvarTy, Ty,
AccountField, AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral,
ConstraintOwner, ConstraintRentExempt, ConstraintSigner, Field, Ty,
};
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()
.map(|f: &Field| {
let name = &f.ident;
quote! {
let #name = next_account_info(acc_infos)?;
}
})
.collect();
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)
.map(|af: &AccountField| match af {
AccountField::AccountsStruct(s) => {
let name = &s.ident;
quote! {
let #name = Accounts::try_accounts(program_id, accounts)?;
}
}
AccountField::Field(f) => {
let name = f.typed_ident();
match f.is_init {
false => quote! {
let #name = Accounts::try_accounts(program_id, accounts)?;
},
true => quote! {
let #name = AccountsInit::try_accounts_init(program_id, accounts)?;
},
}
}
})
.collect();
// Constraint checks for each account fields.
let access_checks: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
// TODO: allow constraints on composite fields.
.filter_map(|af: &AccountField| match af {
AccountField::AccountsStruct(_) => None,
AccountField::Field(f) => Some(f),
})
.map(|f: &Field| {
let checks: Vec<proc_macro2::TokenStream> = f
.constraints
@ -50,8 +55,11 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
let return_tys: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.map(|f: &Field| {
let name = &f.ident;
.map(|f: &AccountField| {
let name = match f {
AccountField::AccountsStruct(s) => &s.ident,
AccountField::Field(f) => &f.ident,
};
quote! {
#name
}
@ -62,26 +70,36 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
let on_save: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.map(|f: &Field| {
let ident = &f.ident;
let info = match f.ty {
Ty::AccountInfo => quote! { #ident },
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
_ => return quote! {},
};
match f.is_mut {
false => quote! {},
true => quote! {
// 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)?;
.map(|af: &AccountField| {
match af {
AccountField::AccountsStruct(s) => {
let name = &s.ident;
quote! {
self.#name.exit(program_id);
}
},
}
AccountField::Field(f) => {
let ident = &f.ident;
let info = match f.ty {
Ty::AccountInfo => quote! { #ident },
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
_ => return quote! {},
};
match f.is_mut {
false => quote! {},
true => quote! {
// 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();
@ -90,10 +108,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.map(|f: &Field| {
let name = &f.ident;
.map(|f: &AccountField| {
let name = match f {
AccountField::AccountsStruct(s) => &s.ident,
AccountField::Field(f) => &f.ident,
};
quote! {
self.#name.to_account_info()
account_infos.extend(self.#name.to_account_infos());
}
})
.collect();
@ -102,19 +123,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
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 },
.map(|f: &AccountField| {
let name = match f {
AccountField::AccountsStruct(s) => &s.ident,
AccountField::Field(f) => &f.ident,
};
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)
},
quote! {
account_metas.extend(self.#name.to_account_metas());
}
})
.collect();
@ -130,15 +145,7 @@ 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, 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..];
fn try_accounts(program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
// Deserialize each account.
#(#deser_fields)*
@ -154,17 +161,22 @@ 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),*
]
let mut account_infos = vec![];
#(#to_acc_infos)*
account_infos
}
}
impl#combined_generics ToAccountMetas for #name#strct_generics {
fn to_account_metas(&self) -> Vec<AccountMeta> {
vec![
#(#to_acc_metas),*
]
let mut account_metas = vec![];
#(#to_acc_metas)*
account_metas
}
}
@ -177,73 +189,6 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
}
}
pub fn generate_field_deserialization(f: &Field) -> proc_macro2::TokenStream {
let ident = &f.ident;
let assign_ty = match &f.ty {
Ty::AccountInfo => quote! {
let #ident = #ident.clone();
},
Ty::ProgramAccount(acc) => {
let account_struct = &acc.account_ident;
match f.is_init {
false => quote! {
let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from(#ident)?;
},
true => quote! {
let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from_init(#ident)?;
},
}
}
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: Sysvar<Clock> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::Rent => quote! {
let #ident: Sysvar<Rent> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::EpochSchedule => quote! {
let #ident: Sysvar<EpochSchedule> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::Fees => quote! {
let #ident: Sysvar<Fees> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::RecentBlockHashes => quote! {
let #ident: Sysvar<RecentBlockhashes> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::SlotHashes => quote! {
let #ident: Sysvar<SlotHashes> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::SlotHistory => quote! {
let #ident: Sysvar<SlotHistory> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::StakeHistory => quote! {
let #ident: Sysvar<StakeHistory> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::Instructions => quote! {
let #ident: Sysvar<Instructions> = Sysvar::from_account_info(#ident)?;
},
SysvarTy::Rewards => quote! {
let #ident: Sysvar<Rewards> = Sysvar::from_account_info(#ident)?;
},
},
};
quote! {
#assign_ty
}
}
pub fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
match c {
Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),

View File

@ -49,7 +49,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
#program_name::#rpc_name(
Context::new(&mut accounts, program_id, remaining_accounts),
Context::new(program_id, &mut accounts, remaining_accounts),
#(#rpc_arg_names),*
)?;
accounts.exit(program_id)

View File

@ -80,6 +80,7 @@ pub enum IdlType {
PublicKey,
Defined(String),
Option(Box<IdlType>),
Vec(Box<IdlType>),
}
#[derive(Debug, Serialize, Deserialize)]
@ -107,7 +108,17 @@ impl std::str::FromStr for IdlType {
"String" => IdlType::String,
"Pubkey" => IdlType::PublicKey,
_ => match s.to_string().strip_prefix("Option<") {
None => IdlType::Defined(s.to_string()),
None => match s.to_string().strip_prefix("Vec<") {
None => IdlType::Defined(s.to_string()),
Some(inner) => {
let inner_ty = Self::from_str(
inner
.strip_suffix(">")
.ok_or(anyhow::anyhow!("Invalid option"))?,
)?;
IdlType::Vec(Box::new(inner_ty))
}
},
Some(inner) => {
let inner_ty = Self::from_str(
inner

View File

@ -1,5 +1,13 @@
//! DSL syntax tokens.
#[cfg(feature = "idl")]
use crate::idl::IdlAccount;
use anyhow::Result;
#[cfg(feature = "idl")]
use heck::MixedCase;
use quote::quote;
use std::collections::HashMap;
pub mod codegen;
#[cfg(feature = "idl")]
pub mod idl;
@ -27,17 +35,18 @@ pub struct RpcArg {
pub raw_arg: syn::PatType,
}
#[derive(Debug)]
pub struct AccountsStruct {
// Name of the accounts struct.
pub ident: syn::Ident,
// Generics + lifetimes on the accounts struct.
pub generics: syn::Generics,
// Fields on the accounts struct.
pub fields: Vec<Field>,
pub fields: Vec<AccountField>,
}
impl AccountsStruct {
pub fn new(strct: syn::ItemStruct, fields: Vec<Field>) -> Self {
pub fn new(strct: syn::ItemStruct, fields: Vec<AccountField>) -> Self {
let ident = strct.ident.clone();
let generics = strct.generics.clone();
Self {
@ -48,18 +57,73 @@ impl AccountsStruct {
}
// Returns all program owned accounts in the Accounts struct.
pub fn account_tys(&self) -> Vec<String> {
//
// `global_accs` is given to "link" account types that are embedded
// in each other.
pub fn account_tys(
&self,
global_accs: &HashMap<String, AccountsStruct>,
) -> Result<Vec<String>> {
let mut tys = vec![];
for f in &self.fields {
match f {
AccountField::Field(f) => {
if let Ty::ProgramAccount(pty) = &f.ty {
tys.push(pty.account_ident.to_string());
}
}
AccountField::AccountsStruct(comp_f) => {
let accs = global_accs.get(&comp_f.symbol).ok_or(anyhow::format_err!(
"Invalid account type: {}",
comp_f.symbol
))?;
tys.extend(accs.account_tys(global_accs)?);
}
}
}
Ok(tys)
}
#[cfg(feature = "idl")]
pub fn idl_accounts(&self, global_accs: &HashMap<String, AccountsStruct>) -> Vec<IdlAccount> {
self.fields
.iter()
.filter_map(|f| match &f.ty {
Ty::ProgramAccount(pty) => Some(pty.account_ident.to_string()),
_ => None,
.flat_map(|acc: &AccountField| match acc {
AccountField::AccountsStruct(comp_f) => {
let accs_strct = global_accs
.get(&comp_f.symbol)
.expect("Could not reslve Accounts symbol");
accs_strct.idl_accounts(global_accs)
}
AccountField::Field(acc) => vec![IdlAccount {
name: acc.ident.to_string().to_mixed_case(),
is_mut: acc.is_mut,
is_signer: acc.is_signer,
}],
})
.collect::<Vec<_>>()
}
}
#[derive(Debug)]
pub enum AccountField {
// Use a `String` instead of the `AccountsStruct` because all
// accounts structs aren't visible to a single derive macro.
//
// When we need the global context, we fill in the String with the
// appropriate values. See, `account_tys` as an example.
AccountsStruct(CompositeField), // Composite
Field(Field), // Primitive
}
#[derive(Debug)]
pub struct CompositeField {
pub ident: syn::Ident,
pub symbol: String,
}
// An account in the accounts struct.
#[derive(Debug)]
pub struct Field {
pub ident: syn::Ident,
pub ty: Ty,
@ -69,16 +133,59 @@ pub struct Field {
pub is_init: bool,
}
impl Field {
pub fn typed_ident(&self) -> proc_macro2::TokenStream {
let name = &self.ident;
let ty = match &self.ty {
Ty::AccountInfo => quote! { AccountInfo },
Ty::ProgramAccount(ty) => {
let account = &ty.account_ident;
quote! {
ProgramAccount<#account>
}
}
Ty::CpiAccount(ty) => {
let account = &ty.account_ident;
quote! {
CpiAccount<#account>
}
}
Ty::Sysvar(ty) => {
let account = match ty {
SysvarTy::Clock => quote! {Clock},
SysvarTy::Rent => quote! {Rent},
SysvarTy::EpochSchedule => quote! {EpochSchedule},
SysvarTy::Fees => quote! {Fees},
SysvarTy::RecentBlockHashes => quote! {RecentBlockHashes},
SysvarTy::SlotHashes => quote! {SlotHashes},
SysvarTy::SlotHistory => quote! {SlotHistory},
SysvarTy::StakeHistory => quote! {StakeHistory},
SysvarTy::Instructions => quote! {Instructions},
SysvarTy::Rewards => quote! {Rewards},
};
quote! {
Sysvar<#account>
}
}
};
quote! {
#name: #ty
}
}
}
// A type of an account field.
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub enum Ty {
AccountInfo,
ProgramAccount(ProgramAccountTy),
Sysvar(SysvarTy),
CpiAccount(CpiAccountTy),
Sysvar(SysvarTy),
}
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub enum SysvarTy {
Clock,
Rent,
@ -92,19 +199,20 @@ pub enum SysvarTy {
Rewards,
}
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub struct ProgramAccountTy {
// The struct type of the account.
pub account_ident: syn::Ident,
}
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub struct CpiAccountTy {
// The struct type of the account.
pub account_ident: syn::Ident,
}
// An access control constraint for an account.
#[derive(Debug)]
pub enum Constraint {
Signer(ConstraintSigner),
BelongsTo(ConstraintBelongsTo),
@ -113,21 +221,26 @@ pub enum Constraint {
RentExempt(ConstraintRentExempt),
}
#[derive(Debug)]
pub struct ConstraintBelongsTo {
pub join_target: proc_macro2::Ident,
}
#[derive(Debug)]
pub struct ConstraintSigner {}
#[derive(Debug)]
pub struct ConstraintLiteral {
pub tokens: proc_macro2::TokenStream,
}
#[derive(Debug)]
pub enum ConstraintOwner {
Program,
Skip,
}
#[derive(Debug)]
pub enum ConstraintRentExempt {
Enforce,
Skip,

View File

@ -1,6 +1,7 @@
use crate::{
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
ConstraintRentExempt, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, SysvarTy, Ty,
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo,
ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSigner, CpiAccountTy,
Field, ProgramAccountTy, SysvarTy, Ty,
};
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
@ -9,7 +10,7 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
_ => panic!("invalid input"),
};
let fields: Vec<Field> = fields
let fields: Vec<AccountField> = fields
.named
.iter()
.map(|f: &syn::Field| {
@ -40,21 +41,34 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
AccountsStruct::new(strct.clone(), fields)
}
// Parses an inert #[anchor] attribute specifying the DSL.
fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> Field {
fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> AccountField {
let ident = f.ident.clone().unwrap();
let ty = parse_ty(f);
let (constraints, is_mut, is_signer, is_init) = match anchor {
None => (vec![], false, false, false),
Some(anchor) => parse_constraints(anchor, &ty),
};
Field {
ident,
ty,
constraints,
is_mut,
is_signer,
is_init,
match is_field_primitive(f) {
true => {
let ty = parse_ty(f);
let (constraints, is_mut, is_signer, is_init) = match anchor {
None => (vec![], false, false, false),
Some(anchor) => parse_constraints(anchor, &ty),
};
AccountField::Field(Field {
ident,
ty,
constraints,
is_mut,
is_signer,
is_init,
})
}
false => AccountField::AccountsStruct(CompositeField {
ident,
symbol: ident_string(f),
}),
}
}
fn is_field_primitive(f: &syn::Field) -> bool {
match ident_string(f).as_str() {
"ProgramAccount" | "CpiAccount" | "Sysvar" | "AccountInfo" => true,
_ => false,
}
}
@ -63,10 +77,7 @@ fn parse_ty(f: &syn::Field) -> Ty {
syn::Type::Path(ty_path) => ty_path.path.clone(),
_ => panic!("invalid account syntax"),
};
// TODO: allow segmented paths.
assert!(path.segments.len() == 1);
let segments = &path.segments[0];
match segments.ident.to_string().as_str() {
match ident_string(f).as_str() {
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
"Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
@ -75,6 +86,17 @@ fn parse_ty(f: &syn::Field) -> Ty {
}
}
fn ident_string(f: &syn::Field) -> String {
let path = match &f.ty {
syn::Type::Path(ty_path) => ty_path.path.clone(),
_ => panic!("invalid account syntax"),
};
// TODO: allow segmented paths.
assert!(path.segments.len() == 1);
let segments = &path.segments[0];
segments.ident.to_string()
}
fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
let account_ident = parse_account(path);
CpiAccountTy { account_ident }

View File

@ -29,7 +29,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
let mut acc_names = HashSet::new();
for accs_strct in accs.values() {
for a in accs_strct.account_tys() {
for a in accs_strct.account_tys(&accs)? {
acc_names.insert(a);
}
}
@ -56,15 +56,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
.collect::<Vec<_>>();
// todo: don't unwrap
let accounts_strct = accs.get(&rpc.anchor_ident.to_string()).unwrap();
let accounts = accounts_strct
.fields
.iter()
.map(|acc| IdlAccount {
name: acc.ident.to_string().to_mixed_case(),
is_mut: acc.is_mut,
is_signer: acc.is_signer,
})
.collect::<Vec<_>>();
let accounts = accounts_strct.idl_accounts(&accs);
IdlInstruction {
name: rpc.ident.to_string().to_mixed_case(),
accounts,
@ -125,7 +117,7 @@ fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
mods[0].clone()
}
// Parse all structs deriving the `Accounts` macro.
// Parse all structs implementing the `Accounts` trait.
fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
f.items
.iter()
@ -139,6 +131,8 @@ fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
}
None
}
// TODO: parse manual implementations. Currently we only look
// for derives.
_ => None,
})
.collect()

View File

@ -1,6 +1,6 @@
{
"name": "@project-serum/anchor",
"version": "0.0.0-alpha.3",
"version": "0.0.0-alpha.4",
"description": "Anchor client",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",

View File

@ -126,7 +126,20 @@ class IdlCoder {
// TODO: all the other types that need to be exported by the borsh package.
default: {
// @ts-ignore
if (field.type.option) {
if (field.type.vec) {
return borsh.vec(
IdlCoder.fieldLayout(
{
name: undefined,
// @ts-ignore
type: field.type.vec,
},
types
),
fieldName
);
// @ts-ignore
} else if (field.type.option) {
return borsh.option(
IdlCoder.fieldLayout(
{

View File

@ -54,9 +54,14 @@ type IdlType =
| "bytes"
| "string"
| "publicKey"
| IdlTypeVec
| IdlTypeOption
| IdlTypeDefined;
export type IdlTypeVec = {
vec: IdlType;
};
export type IdlTypeOption = {
option: IdlType;
};