Composable Accounts derivations (#21)
This commit is contained in:
parent
20bd3c2db8
commit
34a3474663
|
@ -41,6 +41,7 @@ jobs:
|
||||||
name: Runs the examples
|
name: Runs the examples
|
||||||
script:
|
script:
|
||||||
- pushd examples/sysvars && anchor test && popd
|
- pushd examples/sysvars && anchor test && popd
|
||||||
|
- pushd examples/composite && anchor test && popd
|
||||||
- pushd examples/tutorial/basic-0 && anchor test && popd
|
- pushd examples/tutorial/basic-0 && anchor test && popd
|
||||||
- pushd examples/tutorial/basic-1 && anchor test && popd
|
- pushd examples/tutorial/basic-1 && anchor test && popd
|
||||||
- pushd examples/tutorial/basic-2 && anchor test && popd
|
- pushd examples/tutorial/basic-2 && anchor test && popd
|
||||||
|
|
|
@ -43,20 +43,6 @@ dependencies = [
|
||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "anchor-attribute-access-control"
|
name = "anchor-attribute-access-control"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -121,6 +107,20 @@ dependencies = [
|
||||||
"syn 1.0.54",
|
"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]]
|
[[package]]
|
||||||
name = "anchor-syn"
|
name = "anchor-syn"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "anchor"
|
name = "anchor-lang"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = ""
|
description = ""
|
||||||
repository = "https://github.com/project-serum/serum-dex"
|
repository = "https://github.com/project-serum/serum-dex"
|
||||||
|
|
|
@ -55,7 +55,7 @@ mod basic_1 {
|
||||||
pub struct Initialize<'info> {
|
pub struct Initialize<'info> {
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||||
pub rent: Rent,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
|
@ -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(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(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(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("<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. |
|
| `#[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 discriminator_preimage = format!("account:{}", account_name.to_string());
|
||||||
|
|
||||||
let coder = quote! {
|
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> {
|
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
|
||||||
// TODO: we shouldn't have to hash at runtime. However, rust
|
// TODO: we shouldn't have to hash at runtime. However, rust
|
||||||
// is not happy when trying to include solana-sdk from
|
// 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> {
|
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
||||||
let mut discriminator = [0u8; 8];
|
let mut discriminator = [0u8; 8];
|
||||||
discriminator.copy_from_slice(
|
discriminator.copy_from_slice(
|
||||||
|
|
|
@ -30,7 +30,7 @@ cpi = ["no-entrypoint"]
|
||||||
borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
|
borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = {{ version = "1.3.14", default-features = false, features = ["program"] }}
|
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,
|
||||||
name.to_snake_case(),
|
name.to_snake_case(),
|
||||||
|
@ -47,7 +47,7 @@ pub fn lib_rs(name: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"#![feature(proc_macro_hygiene)]
|
r#"#![feature(proc_macro_hygiene)]
|
||||||
|
|
||||||
use anchor::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
#[program]
|
#[program]
|
||||||
mod {} {{
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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)]
|
#![feature(proc_macro_hygiene)]
|
||||||
|
|
||||||
use anchor::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
// Define the program's RPC handlers.
|
// Define the program's instruction handlers.
|
||||||
|
|
||||||
#[program]
|
#[program]
|
||||||
mod basic_2 {
|
mod basic_2 {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[access_control(not_zero(authority))]
|
pub fn create_author(
|
||||||
pub fn create_root(ctx: Context<CreateRoot>, authority: Pubkey, data: u64) -> ProgramResult {
|
ctx: Context<CreateAuthor>,
|
||||||
let root = &mut ctx.accounts.root;
|
authority: Pubkey,
|
||||||
root.authority = authority;
|
name: String,
|
||||||
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>,
|
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
let leaf = &mut ctx.accounts.leaf;
|
let author = &mut ctx.accounts.author;
|
||||||
leaf.data = data;
|
author.authority = authority;
|
||||||
if let Some(custom) = custom {
|
author.name = name;
|
||||||
leaf.custom = custom;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -47,66 +52,60 @@ mod basic_2 {
|
||||||
// Define the validated accounts for each handler.
|
// Define the validated accounts for each handler.
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct CreateRoot<'info> {
|
pub struct CreateAuthor<'info> {
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub author: ProgramAccount<'info, Author>,
|
||||||
pub rent: Sysvar<'info, Rent>,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct UpdateRoot<'info> {
|
pub struct UpdateAuthor<'info> {
|
||||||
#[account(signer)]
|
#[account(signer)]
|
||||||
pub authority: AccountInfo<'info>,
|
pub authority: AccountInfo<'info>,
|
||||||
#[account(mut, "&root.authority == authority.key")]
|
#[account(mut, "&author.authority == authority.key")]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub author: ProgramAccount<'info, Author>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct CreateLeaf<'info> {
|
pub struct CreateBook<'info> {
|
||||||
pub root: ProgramAccount<'info, Root>,
|
#[account(signer)]
|
||||||
|
pub authority: AccountInfo<'info>,
|
||||||
|
#[account("&author.authority == authority.key")]
|
||||||
|
pub author: ProgramAccount<'info, Author>,
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
pub leaf: ProgramAccount<'info, Leaf>,
|
pub book: ProgramAccount<'info, Book>,
|
||||||
pub rent: Sysvar<'info, Rent>,
|
pub rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct UpdateLeaf<'info> {
|
pub struct UpdateBook<'info> {
|
||||||
#[account(signer)]
|
#[account(signer)]
|
||||||
pub authority: AccountInfo<'info>,
|
pub authority: AccountInfo<'info>,
|
||||||
#[account("&root.authority == authority.key")]
|
#[account("&author.authority == authority.key")]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub author: ProgramAccount<'info, Author>,
|
||||||
#[account(mut, belongs_to = root)]
|
#[account(mut, belongs_to = author)]
|
||||||
pub leaf: ProgramAccount<'info, Leaf>,
|
pub book: ProgramAccount<'info, Book>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the program owned accounts.
|
// Define the program owned accounts.
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Root {
|
pub struct Author {
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
pub data: u64,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Leaf {
|
pub struct Book {
|
||||||
pub root: Pubkey,
|
pub author: Pubkey,
|
||||||
pub data: u64,
|
pub title: String,
|
||||||
pub custom: MyCustomType,
|
pub pages: Vec<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define custom types.
|
// Define custom types.
|
||||||
|
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||||
pub struct MyCustomType {
|
pub struct Page {
|
||||||
pub my_data: u64,
|
pub content: String,
|
||||||
pub key: Pubkey,
|
pub footnote: String,
|
||||||
}
|
|
||||||
|
|
||||||
// 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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// Configure the client to use the local cluster.
|
||||||
anchor.setProvider(anchor.Provider.local());
|
anchor.setProvider(provider);
|
||||||
|
|
||||||
it('Applies constraints and access control', async () => {
|
// Author for the tests.
|
||||||
const program = anchor.workspace.Basic2;
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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"] }
|
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"] }
|
borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
|
||||||
solana-program = "1.4.3"
|
solana-program = "1.4.3"
|
||||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
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::account_info::AccountInfo;
|
||||||
use solana_sdk::instruction::AccountMeta;
|
use solana_sdk::instruction::AccountMeta;
|
||||||
use solana_sdk::program_error::ProgramError;
|
use solana_sdk::program_error::ProgramError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
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_access_control::access_control;
|
||||||
pub use anchor_attribute_account::account;
|
pub use anchor_attribute_account::account;
|
||||||
pub use anchor_attribute_program::program;
|
pub use anchor_attribute_program::program;
|
||||||
|
@ -12,7 +44,7 @@ pub use anchor_derive_accounts::Accounts;
|
||||||
/// Default serialization format for anchor instructions and accounts.
|
/// Default serialization format for anchor instructions and accounts.
|
||||||
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
||||||
|
|
||||||
/// A data structure of Solana accounts that can be deserialized from the input
|
/// A data structure of accounts that can be deserialized from the input
|
||||||
/// of a Solana program. Due to the freewheeling nature of the accounts array,
|
/// of a Solana program. Due to the freewheeling nature of the accounts array,
|
||||||
/// implementations of this trait should perform any and all constraint checks
|
/// implementations of this trait should perform any and all constraint checks
|
||||||
/// (in addition to any done within `AccountDeserialize`) on accounts to ensure
|
/// (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 {
|
pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
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>;
|
) -> Result<Self, ProgramError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,177 +110,13 @@ pub trait AccountDeserialize: Sized {
|
||||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Container for a serializable `account`. Use this to reference any account
|
/// The prelude contains all commonly used components of the crate.
|
||||||
/// owned by the currently executing program.
|
/// All programs should include it via `anchor_lang::prelude::*;`.
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
|
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
|
||||||
AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, ProgramAccount,
|
AccountsInit, AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext,
|
||||||
Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
ProgramAccount, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use solana_program::msg;
|
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::{
|
use crate::{
|
||||||
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
|
AccountField, AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral,
|
||||||
ConstraintRentExempt, ConstraintSigner, Field, SysvarTy, Ty,
|
ConstraintOwner, ConstraintRentExempt, ConstraintSigner, Field, Ty,
|
||||||
};
|
};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
// Extract out each account info.
|
|
||||||
let acc_infos: Vec<proc_macro2::TokenStream> = accs
|
|
||||||
.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.
|
// Deserialization for each field.
|
||||||
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.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();
|
.collect();
|
||||||
|
|
||||||
// Constraint checks for each account fields.
|
// Constraint checks for each account fields.
|
||||||
let access_checks: Vec<proc_macro2::TokenStream> = accs
|
let access_checks: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
|
// TODO: allow constraints on composite fields.
|
||||||
|
.filter_map(|af: &AccountField| match af {
|
||||||
|
AccountField::AccountsStruct(_) => None,
|
||||||
|
AccountField::Field(f) => Some(f),
|
||||||
|
})
|
||||||
.map(|f: &Field| {
|
.map(|f: &Field| {
|
||||||
let checks: Vec<proc_macro2::TokenStream> = f
|
let checks: Vec<proc_macro2::TokenStream> = f
|
||||||
.constraints
|
.constraints
|
||||||
|
@ -50,8 +55,11 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &Field| {
|
.map(|f: &AccountField| {
|
||||||
let name = &f.ident;
|
let name = match f {
|
||||||
|
AccountField::AccountsStruct(s) => &s.ident,
|
||||||
|
AccountField::Field(f) => &f.ident,
|
||||||
|
};
|
||||||
quote! {
|
quote! {
|
||||||
#name
|
#name
|
||||||
}
|
}
|
||||||
|
@ -62,26 +70,36 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let on_save: Vec<proc_macro2::TokenStream> = accs
|
let on_save: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &Field| {
|
.map(|af: &AccountField| {
|
||||||
let ident = &f.ident;
|
match af {
|
||||||
let info = match f.ty {
|
AccountField::AccountsStruct(s) => {
|
||||||
Ty::AccountInfo => quote! { #ident },
|
let name = &s.ident;
|
||||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
quote! {
|
||||||
_ => return quote! {},
|
self.#name.exit(program_id);
|
||||||
};
|
|
||||||
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)?;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
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();
|
.collect();
|
||||||
|
@ -90,10 +108,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
|
let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &Field| {
|
.map(|f: &AccountField| {
|
||||||
let name = &f.ident;
|
let name = match f {
|
||||||
|
AccountField::AccountsStruct(s) => &s.ident,
|
||||||
|
AccountField::Field(f) => &f.ident,
|
||||||
|
};
|
||||||
quote! {
|
quote! {
|
||||||
self.#name.to_account_info()
|
account_infos.extend(self.#name.to_account_infos());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -102,19 +123,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
|
let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &Field| {
|
.map(|f: &AccountField| {
|
||||||
let name = &f.ident;
|
let name = match f {
|
||||||
let is_signer = match f.is_signer {
|
AccountField::AccountsStruct(s) => &s.ident,
|
||||||
false => quote! { false },
|
AccountField::Field(f) => &f.ident,
|
||||||
true => quote! { true },
|
|
||||||
};
|
};
|
||||||
match f.is_mut {
|
quote! {
|
||||||
false => quote! {
|
account_metas.extend(self.#name.to_account_metas());
|
||||||
AccountMeta::new_readonly(*self.#name.to_account_info().key, #is_signer)
|
|
||||||
},
|
|
||||||
true => quote! {
|
|
||||||
AccountMeta::new(*self.#name.to_account_info().key, #is_signer)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -130,15 +145,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
||||||
fn try_accounts(program_id: &Pubkey, remaining_accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
fn try_accounts(program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||||
let acc_infos = &mut remaining_accounts.iter();
|
|
||||||
|
|
||||||
// Pull out each account info from the `accounts` slice.
|
|
||||||
#(#acc_infos)*
|
|
||||||
|
|
||||||
// Move the remaining_accounts cursor to the iterator end.
|
|
||||||
*remaining_accounts = &remaining_accounts[#acc_infos_len..];
|
|
||||||
|
|
||||||
// Deserialize each account.
|
// Deserialize each account.
|
||||||
#(#deser_fields)*
|
#(#deser_fields)*
|
||||||
|
|
||||||
|
@ -154,17 +161,22 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
|
|
||||||
impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
|
impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
|
||||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||||
vec![
|
let mut account_infos = vec![];
|
||||||
#(#to_acc_infos),*
|
|
||||||
]
|
#(#to_acc_infos)*
|
||||||
|
|
||||||
|
account_infos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl#combined_generics ToAccountMetas for #name#strct_generics {
|
impl#combined_generics ToAccountMetas for #name#strct_generics {
|
||||||
fn to_account_metas(&self) -> Vec<AccountMeta> {
|
fn to_account_metas(&self) -> Vec<AccountMeta> {
|
||||||
vec![
|
let mut account_metas = vec![];
|
||||||
#(#to_acc_metas),*
|
|
||||||
]
|
#(#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 {
|
pub fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
|
||||||
match c {
|
match c {
|
||||||
Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, 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 remaining_accounts: &[AccountInfo] = accounts;
|
||||||
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
|
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
|
||||||
#program_name::#rpc_name(
|
#program_name::#rpc_name(
|
||||||
Context::new(&mut accounts, program_id, remaining_accounts),
|
Context::new(program_id, &mut accounts, remaining_accounts),
|
||||||
#(#rpc_arg_names),*
|
#(#rpc_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
accounts.exit(program_id)
|
accounts.exit(program_id)
|
||||||
|
|
|
@ -80,6 +80,7 @@ pub enum IdlType {
|
||||||
PublicKey,
|
PublicKey,
|
||||||
Defined(String),
|
Defined(String),
|
||||||
Option(Box<IdlType>),
|
Option(Box<IdlType>),
|
||||||
|
Vec(Box<IdlType>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -107,7 +108,17 @@ impl std::str::FromStr for IdlType {
|
||||||
"String" => IdlType::String,
|
"String" => IdlType::String,
|
||||||
"Pubkey" => IdlType::PublicKey,
|
"Pubkey" => IdlType::PublicKey,
|
||||||
_ => match s.to_string().strip_prefix("Option<") {
|
_ => 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) => {
|
Some(inner) => {
|
||||||
let inner_ty = Self::from_str(
|
let inner_ty = Self::from_str(
|
||||||
inner
|
inner
|
||||||
|
|
135
syn/src/lib.rs
135
syn/src/lib.rs
|
@ -1,5 +1,13 @@
|
||||||
//! DSL syntax tokens.
|
//! 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;
|
pub mod codegen;
|
||||||
#[cfg(feature = "idl")]
|
#[cfg(feature = "idl")]
|
||||||
pub mod idl;
|
pub mod idl;
|
||||||
|
@ -27,17 +35,18 @@ pub struct RpcArg {
|
||||||
pub raw_arg: syn::PatType,
|
pub raw_arg: syn::PatType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AccountsStruct {
|
pub struct AccountsStruct {
|
||||||
// Name of the accounts struct.
|
// Name of the accounts struct.
|
||||||
pub ident: syn::Ident,
|
pub ident: syn::Ident,
|
||||||
// Generics + lifetimes on the accounts struct.
|
// Generics + lifetimes on the accounts struct.
|
||||||
pub generics: syn::Generics,
|
pub generics: syn::Generics,
|
||||||
// Fields on the accounts struct.
|
// Fields on the accounts struct.
|
||||||
pub fields: Vec<Field>,
|
pub fields: Vec<AccountField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountsStruct {
|
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 ident = strct.ident.clone();
|
||||||
let generics = strct.generics.clone();
|
let generics = strct.generics.clone();
|
||||||
Self {
|
Self {
|
||||||
|
@ -48,18 +57,73 @@ impl AccountsStruct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns all program owned accounts in the Accounts struct.
|
// 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
|
self.fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|f| match &f.ty {
|
.flat_map(|acc: &AccountField| match acc {
|
||||||
Ty::ProgramAccount(pty) => Some(pty.account_ident.to_string()),
|
AccountField::AccountsStruct(comp_f) => {
|
||||||
_ => None,
|
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<_>>()
|
.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.
|
// An account in the accounts struct.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub ident: syn::Ident,
|
pub ident: syn::Ident,
|
||||||
pub ty: Ty,
|
pub ty: Ty,
|
||||||
|
@ -69,16 +133,59 @@ pub struct Field {
|
||||||
pub is_init: bool,
|
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.
|
// A type of an account field.
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Ty {
|
pub enum Ty {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
ProgramAccount(ProgramAccountTy),
|
ProgramAccount(ProgramAccountTy),
|
||||||
Sysvar(SysvarTy),
|
|
||||||
CpiAccount(CpiAccountTy),
|
CpiAccount(CpiAccountTy),
|
||||||
|
Sysvar(SysvarTy),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SysvarTy {
|
pub enum SysvarTy {
|
||||||
Clock,
|
Clock,
|
||||||
Rent,
|
Rent,
|
||||||
|
@ -92,19 +199,20 @@ pub enum SysvarTy {
|
||||||
Rewards,
|
Rewards,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ProgramAccountTy {
|
pub struct ProgramAccountTy {
|
||||||
// The struct type of the account.
|
// The struct type of the account.
|
||||||
pub account_ident: syn::Ident,
|
pub account_ident: syn::Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct CpiAccountTy {
|
pub struct CpiAccountTy {
|
||||||
// The struct type of the account.
|
// The struct type of the account.
|
||||||
pub account_ident: syn::Ident,
|
pub account_ident: syn::Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
// An access control constraint for an account.
|
// An access control constraint for an account.
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Constraint {
|
pub enum Constraint {
|
||||||
Signer(ConstraintSigner),
|
Signer(ConstraintSigner),
|
||||||
BelongsTo(ConstraintBelongsTo),
|
BelongsTo(ConstraintBelongsTo),
|
||||||
|
@ -113,21 +221,26 @@ pub enum Constraint {
|
||||||
RentExempt(ConstraintRentExempt),
|
RentExempt(ConstraintRentExempt),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ConstraintBelongsTo {
|
pub struct ConstraintBelongsTo {
|
||||||
pub join_target: proc_macro2::Ident,
|
pub join_target: proc_macro2::Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ConstraintSigner {}
|
pub struct ConstraintSigner {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ConstraintLiteral {
|
pub struct ConstraintLiteral {
|
||||||
pub tokens: proc_macro2::TokenStream,
|
pub tokens: proc_macro2::TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ConstraintOwner {
|
pub enum ConstraintOwner {
|
||||||
Program,
|
Program,
|
||||||
Skip,
|
Skip,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ConstraintRentExempt {
|
pub enum ConstraintRentExempt {
|
||||||
Enforce,
|
Enforce,
|
||||||
Skip,
|
Skip,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
|
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo,
|
||||||
ConstraintRentExempt, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, SysvarTy, Ty,
|
ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSigner, CpiAccountTy,
|
||||||
|
Field, ProgramAccountTy, SysvarTy, Ty,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
|
@ -9,7 +10,7 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
_ => panic!("invalid input"),
|
_ => panic!("invalid input"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let fields: Vec<Field> = fields
|
let fields: Vec<AccountField> = fields
|
||||||
.named
|
.named
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &syn::Field| {
|
.map(|f: &syn::Field| {
|
||||||
|
@ -40,21 +41,34 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
AccountsStruct::new(strct.clone(), fields)
|
AccountsStruct::new(strct.clone(), fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses an inert #[anchor] attribute specifying the DSL.
|
fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> AccountField {
|
||||||
fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> Field {
|
|
||||||
let ident = f.ident.clone().unwrap();
|
let ident = f.ident.clone().unwrap();
|
||||||
let ty = parse_ty(f);
|
match is_field_primitive(f) {
|
||||||
let (constraints, is_mut, is_signer, is_init) = match anchor {
|
true => {
|
||||||
None => (vec![], false, false, false),
|
let ty = parse_ty(f);
|
||||||
Some(anchor) => parse_constraints(anchor, &ty),
|
let (constraints, is_mut, is_signer, is_init) = match anchor {
|
||||||
};
|
None => (vec![], false, false, false),
|
||||||
Field {
|
Some(anchor) => parse_constraints(anchor, &ty),
|
||||||
ident,
|
};
|
||||||
ty,
|
AccountField::Field(Field {
|
||||||
constraints,
|
ident,
|
||||||
is_mut,
|
ty,
|
||||||
is_signer,
|
constraints,
|
||||||
is_init,
|
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(),
|
syn::Type::Path(ty_path) => ty_path.path.clone(),
|
||||||
_ => panic!("invalid account syntax"),
|
_ => panic!("invalid account syntax"),
|
||||||
};
|
};
|
||||||
// TODO: allow segmented paths.
|
match ident_string(f).as_str() {
|
||||||
assert!(path.segments.len() == 1);
|
|
||||||
let segments = &path.segments[0];
|
|
||||||
match segments.ident.to_string().as_str() {
|
|
||||||
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
|
"ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
|
||||||
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
|
"CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
|
||||||
"Sysvar" => Ty::Sysvar(parse_sysvar(&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 {
|
fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
|
||||||
let account_ident = parse_account(path);
|
let account_ident = parse_account(path);
|
||||||
CpiAccountTy { account_ident }
|
CpiAccountTy { account_ident }
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
||||||
let mut acc_names = HashSet::new();
|
let mut acc_names = HashSet::new();
|
||||||
|
|
||||||
for accs_strct in accs.values() {
|
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);
|
acc_names.insert(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,15 +56,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
// todo: don't unwrap
|
// todo: don't unwrap
|
||||||
let accounts_strct = accs.get(&rpc.anchor_ident.to_string()).unwrap();
|
let accounts_strct = accs.get(&rpc.anchor_ident.to_string()).unwrap();
|
||||||
let accounts = accounts_strct
|
let accounts = accounts_strct.idl_accounts(&accs);
|
||||||
.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<_>>();
|
|
||||||
IdlInstruction {
|
IdlInstruction {
|
||||||
name: rpc.ident.to_string().to_mixed_case(),
|
name: rpc.ident.to_string().to_mixed_case(),
|
||||||
accounts,
|
accounts,
|
||||||
|
@ -125,7 +117,7 @@ fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
|
||||||
mods[0].clone()
|
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> {
|
fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
|
||||||
f.items
|
f.items
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -139,6 +131,8 @@ fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
// TODO: parse manual implementations. Currently we only look
|
||||||
|
// for derives.
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@project-serum/anchor",
|
"name": "@project-serum/anchor",
|
||||||
"version": "0.0.0-alpha.3",
|
"version": "0.0.0-alpha.4",
|
||||||
"description": "Anchor client",
|
"description": "Anchor client",
|
||||||
"main": "dist/cjs/index.js",
|
"main": "dist/cjs/index.js",
|
||||||
"module": "dist/esm/index.js",
|
"module": "dist/esm/index.js",
|
||||||
|
|
|
@ -126,7 +126,20 @@ class IdlCoder {
|
||||||
// TODO: all the other types that need to be exported by the borsh package.
|
// TODO: all the other types that need to be exported by the borsh package.
|
||||||
default: {
|
default: {
|
||||||
// @ts-ignore
|
// @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(
|
return borsh.option(
|
||||||
IdlCoder.fieldLayout(
|
IdlCoder.fieldLayout(
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,9 +54,14 @@ type IdlType =
|
||||||
| "bytes"
|
| "bytes"
|
||||||
| "string"
|
| "string"
|
||||||
| "publicKey"
|
| "publicKey"
|
||||||
|
| IdlTypeVec
|
||||||
| IdlTypeOption
|
| IdlTypeOption
|
||||||
| IdlTypeDefined;
|
| IdlTypeDefined;
|
||||||
|
|
||||||
|
export type IdlTypeVec = {
|
||||||
|
vec: IdlType;
|
||||||
|
};
|
||||||
|
|
||||||
export type IdlTypeOption = {
|
export type IdlTypeOption = {
|
||||||
option: IdlType;
|
option: IdlType;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue