Composable Accounts derivations (#21)
This commit is contained in:
parent
20bd3c2db8
commit
34a3474663
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "anchor"
|
||||
name = "anchor-lang"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
repository = "https://github.com/project-serum/serum-dex"
|
||||
|
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {} {{
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
cluster = "localnet"
|
||||
wallet = "/home/armaniferrante/.config/solana/id.json"
|
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -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"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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,
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
221
src/lib.rs
221
src/lib.rs
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
135
syn/src/lib.rs
135
syn/src/lib.rs
|
@ -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,
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(
|
||||
{
|
||||
|
|
|
@ -54,9 +54,14 @@ type IdlType =
|
|||
| "bytes"
|
||||
| "string"
|
||||
| "publicKey"
|
||||
| IdlTypeVec
|
||||
| IdlTypeOption
|
||||
| IdlTypeDefined;
|
||||
|
||||
export type IdlTypeVec = {
|
||||
vec: IdlType;
|
||||
};
|
||||
|
||||
export type IdlTypeOption = {
|
||||
option: IdlType;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue