Account discriminators (#14)
This commit is contained in:
parent
4792d1ce49
commit
de353cb4e4
|
@ -21,7 +21,7 @@ _examples: &examples
|
||||||
- export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
|
- export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
|
||||||
- export NODE_PATH="/home/travis/.nvm/versions/node/v$NODE_VERSION/lib/node_modules/:$NODE_PATH"
|
- export NODE_PATH="/home/travis/.nvm/versions/node/v$NODE_VERSION/lib/node_modules/:$NODE_PATH"
|
||||||
- yes | solana-keygen new
|
- yes | solana-keygen new
|
||||||
- cargo install --git https://github.com/project-serum/anchor anchor-cli
|
- cargo install --git https://github.com/project-serum/anchor anchor-cli --locked
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
|
|
|
@ -47,9 +47,10 @@ dependencies = [
|
||||||
name = "anchor"
|
name = "anchor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-attributes-access-control",
|
"anchor-attribute-access-control",
|
||||||
"anchor-attributes-program",
|
"anchor-attribute-account",
|
||||||
"anchor-derive",
|
"anchor-attribute-program",
|
||||||
|
"anchor-derive-accounts",
|
||||||
"borsh",
|
"borsh",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
|
@ -57,7 +58,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anchor-attributes-access-control"
|
name = "anchor-attribute-access-control"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-syn",
|
"anchor-syn",
|
||||||
|
@ -68,7 +69,18 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anchor-attributes-program"
|
name = "anchor-attribute-account"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anchor-syn",
|
||||||
|
"anyhow",
|
||||||
|
"proc-macro2 1.0.24",
|
||||||
|
"quote 1.0.8",
|
||||||
|
"syn 1.0.54",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anchor-attribute-program"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-syn",
|
"anchor-syn",
|
||||||
|
@ -99,7 +111,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anchor-derive"
|
name = "anchor-derive-accounts"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-syn",
|
"anchor-syn",
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -13,15 +13,16 @@ default = []
|
||||||
thiserror = "1.0.20"
|
thiserror = "1.0.20"
|
||||||
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-derive = { path = "./derive" }
|
anchor-derive-accounts = { path = "./derive/accounts" }
|
||||||
anchor-attributes-program = { path = "./attributes/program" }
|
anchor-attribute-program = { path = "./attribute/program" }
|
||||||
anchor-attributes-access-control = { path = "./attributes/access-control" }
|
anchor-attribute-access-control = { path = "./attribute/access-control" }
|
||||||
|
anchor-attribute-account = { path = "./attribute/account" }
|
||||||
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"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"cli",
|
"cli",
|
||||||
"syn",
|
"syn",
|
||||||
"attributes/*",
|
"attribute/*",
|
||||||
"derive",
|
"derive/*",
|
||||||
]
|
]
|
||||||
|
|
|
@ -91,6 +91,7 @@ purposes of the Accounts macro) that can be specified on a struct deriving `Acco
|
||||||
|:--|:--|:--|
|
|:--|:--|:--|
|
||||||
| `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
|
| `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
|
||||||
| `#[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(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the accounts array. |
|
| `#[account(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the accounts array. |
|
||||||
| `#[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 `ProgramAccount` and `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. |
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "anchor-attributes-access-control"
|
name = "anchor-attribute-access-control"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
|
@ -3,6 +3,9 @@ extern crate proc_macro;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
|
/// Executes the given access control method before running the decorated
|
||||||
|
/// instruction handler. Any method in scope of the attribute can be invoked
|
||||||
|
/// with any arguments from the associated instruction handler.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn access_control(
|
pub fn access_control(
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "anchor-attributes-program"
|
name = "anchor-attribute-account"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
@ -12,4 +12,4 @@ proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0.54", features = ["full"] }
|
syn = { version = "1.0.54", features = ["full"] }
|
||||||
anyhow = "1.0.32"
|
anyhow = "1.0.32"
|
||||||
anchor-syn = { path = "../../syn" }
|
anchor-syn = { path = "../../syn" }
|
|
@ -0,0 +1,75 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use quote::quote;
|
||||||
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
|
/// A data structure representing a Solana account.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn account(
|
||||||
|
_args: proc_macro::TokenStream,
|
||||||
|
input: proc_macro::TokenStream,
|
||||||
|
) -> proc_macro::TokenStream {
|
||||||
|
let account_strct = parse_macro_input!(input as syn::ItemStruct);
|
||||||
|
let account_name = &account_strct.ident;
|
||||||
|
// Namespace the discriminator to prevent future collisions, e.g.,
|
||||||
|
// if we (for some unforseen reason) wanted to hash other parts of the
|
||||||
|
// program.
|
||||||
|
let discriminator_preimage = format!("account:{}", account_name.to_string());
|
||||||
|
|
||||||
|
let coder = quote! {
|
||||||
|
impl anchor::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
|
||||||
|
// the proc-macro crate.
|
||||||
|
let mut discriminator = [0u8; 8];
|
||||||
|
discriminator.copy_from_slice(
|
||||||
|
&solana_program::hash::hash(
|
||||||
|
#discriminator_preimage.as_bytes(),
|
||||||
|
).to_bytes()[..8],
|
||||||
|
);
|
||||||
|
|
||||||
|
writer.write_all(&discriminator).map_err(|_| ProgramError::InvalidAccountData)?;
|
||||||
|
AnchorSerialize::serialize(
|
||||||
|
self,
|
||||||
|
writer
|
||||||
|
)
|
||||||
|
.map_err(|_| ProgramError::InvalidAccountData)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl anchor::AccountDeserialize for #account_name {
|
||||||
|
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
||||||
|
let mut discriminator = [0u8; 8];
|
||||||
|
discriminator.copy_from_slice(
|
||||||
|
&solana_program::hash::hash(
|
||||||
|
#discriminator_preimage.as_bytes(),
|
||||||
|
).to_bytes()[..8],
|
||||||
|
);
|
||||||
|
|
||||||
|
if buf.len() < discriminator.len() {
|
||||||
|
return Err(ProgramError::AccountDataTooSmall);
|
||||||
|
}
|
||||||
|
let given_disc = &buf[..8];
|
||||||
|
if &discriminator != given_disc {
|
||||||
|
return Err(ProgramError::InvalidInstructionData);
|
||||||
|
}
|
||||||
|
Self::try_deserialize_unchecked(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
||||||
|
let mut data: &[u8] = &buf[8..];
|
||||||
|
AnchorDeserialize::deserialize(&mut data)
|
||||||
|
.map_err(|_| ProgramError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
proc_macro::TokenStream::from(quote! {
|
||||||
|
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||||
|
#account_strct
|
||||||
|
|
||||||
|
#coder
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "anchor-attribute-program"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = { version = "1.0.54", features = ["full"] }
|
||||||
|
anyhow = "1.0.32"
|
||||||
|
anchor-syn = { path = "../../syn" }
|
|
@ -4,6 +4,8 @@ use anchor_syn::codegen::program as program_codegen;
|
||||||
use anchor_syn::parser::program as program_parser;
|
use anchor_syn::parser::program as program_parser;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
|
/// The module containing all instruction handlers defining all entries to the
|
||||||
|
/// Solana program.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn program(
|
pub fn program(
|
||||||
_args: proc_macro::TokenStream,
|
_args: proc_macro::TokenStream,
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "anchor-derive"
|
name = "anchor-derive-accounts"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
@ -12,4 +12,4 @@ proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0.54", features = ["full"] }
|
syn = { version = "1.0.54", features = ["full"] }
|
||||||
anyhow = "1.0.32"
|
anyhow = "1.0.32"
|
||||||
anchor-syn = { path = "../syn" }
|
anchor-syn = { path = "../../syn" }
|
|
@ -34,7 +34,7 @@ npm install -g mocha
|
||||||
For now, we can use Cargo to install the CLI.
|
For now, we can use Cargo to install the CLI.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install --git https://github.com/project-serum/anchor anchor-cli
|
cargo install --git https://github.com/project-serum/anchor anchor-cli --locked
|
||||||
```
|
```
|
||||||
|
|
||||||
On Linux systems you may need to install additional dependencies. On Ubuntu,
|
On Linux systems you may need to install additional dependencies. On Ubuntu,
|
||||||
|
|
|
@ -100,7 +100,7 @@ Once built, we can deploy the program by running
|
||||||
anchor deploy
|
anchor deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
Take note of program's deployed address. We'll use it next.
|
Take note of the program's deployed address. We'll use it next.
|
||||||
|
|
||||||
## Generating a Client
|
## Generating a Client
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Tutorial 1: Accounts, Arguments, and Types
|
# Tutorial 1: Arguments and Accounts
|
||||||
|
|
||||||
This tutorial covers the basics of creating and mutating accounts using Anchor.
|
This tutorial covers the basics of creating and mutating accounts using Anchor.
|
||||||
It's recommended to read [Tutorial 0](./tutorial-0.md) first, as this tutorial will
|
It's recommended to read [Tutorial 0](./tutorial-0.md) first, as this tutorial will
|
||||||
|
@ -22,25 +22,56 @@ cd anchor/examples/tutorial/basic-1
|
||||||
|
|
||||||
We define our program as follows
|
We define our program as follows
|
||||||
|
|
||||||
<<< @/../examples/tutorial/basic-1/programs/basic-1/src/lib.rs#program
|
<<< @/../examples/tutorial/basic-1/programs/basic-1/src/lib.rs
|
||||||
|
|
||||||
Some new syntax elements are introduced here.
|
Some new syntax elements are introduced here.
|
||||||
|
|
||||||
First, notice the `data` argument passed into the program. This argument and any other valid
|
### `initialize` instruction
|
||||||
Rust types can be passed to the instruction to define inputs to the program. If you'd like to
|
|
||||||
pass in your own type, then it must be defined in the same `src/lib.rs` file as the
|
First, let's start with the initialize instruction. Notice the `data` argument passed into the program. This argument and any other valid
|
||||||
`#[program]` module (so that the IDL can pick it up). Additionally,
|
Rust types can be passed to the instruction to define inputs to the program.
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
If you'd like to pass in your own type as an input to an instruction handler, then it must be
|
||||||
|
defined in the same `src/lib.rs` file as the `#[program]` module, so that the IDL parser can
|
||||||
|
pick it up.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Additionally,
|
||||||
notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us
|
notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us
|
||||||
the `Initialize` struct, deriving `Accounts`.
|
the `Initialize` struct, deriving `Accounts`. There are two things to notice about `Initialize`.
|
||||||
|
|
||||||
There are two things to notice about `Initialize`. First, the
|
1. The `my_account` field is of type `ProgramAccount<'info, MyAccount>`, telling the program it *must*
|
||||||
`my_account` field is marked with the `#[account(mut)]` attribute. This means that any
|
be **owned** by the currently executing program, and the deserialized data structure is `MyAccount`.
|
||||||
changes to the field will be persisted upon exiting the program. Second, the field is of
|
2. The `my_account` field is marked with the `#[account(init)]` attribute. This should be used
|
||||||
type `ProgramAccount<'info, MyAccount>`, telling the program it *must* be **owned**
|
in one situation: when a given `ProgramAccount` is newly created and is being used by the program
|
||||||
by the currently executing program and the deserialized data structure is `MyAccount`.
|
for the first time (and thus it's data field is all zero). If `#[account(init)]` is not used
|
||||||
|
when account data is zero initialized, the transaction will be rejected.
|
||||||
|
|
||||||
In a later tutorial we'll delve more deeply into deriving `Accounts`. For now, just know
|
::: details
|
||||||
one must mark their accounts `mut` if they want them to, well, mutate. ;)
|
All accounts created with Anchor are laid out as follows: `8-byte-discriminator || borsh
|
||||||
|
serialized data`. The 8-byte-discriminator is created from the first 8 bytes of the
|
||||||
|
`Sha256` hash of the account's type--using the example above, `sha256("MyAccount")[..8]`.
|
||||||
|
|
||||||
|
Importantly, this allows a program to know for certain an account is indeed of a given type.
|
||||||
|
Without it, a program would be vulnerable to account injection attacks, where a malicious user
|
||||||
|
specifies an account of an unexpected type, causing the program to do unexpected things.
|
||||||
|
|
||||||
|
On account creation, this 8-byte discriminator doesn't exist, since the account storage is
|
||||||
|
zeroed. The first time an Anchor program mutates an account, this discriminator is prepended
|
||||||
|
to the account storage array and all subsequent accesses to the account (not decorated with
|
||||||
|
`#[account(init)]`) will check for this discriminator.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### `update` instruction
|
||||||
|
|
||||||
|
Similarly, the `Update` accounts struct is marked with the `#[account(mut)]` attribute.
|
||||||
|
Marking an account as `mut` persists any changes made upon exiting the program.
|
||||||
|
|
||||||
|
Here we've covered the basics of how to interact with accounts. In a later tutorial,
|
||||||
|
we'll delve more deeply into deriving `Accounts`, but for now, just know
|
||||||
|
one must mark their accounts `init` when using an account for the first time and `mut`
|
||||||
|
for persisting changes.
|
||||||
|
|
||||||
## Creating and Initializing Accounts
|
## Creating and Initializing Accounts
|
||||||
|
|
||||||
|
@ -74,8 +105,8 @@ which in this case is `initialize`. Because we are creating `myAccount`, it need
|
||||||
sign the transaction, as required by the Solana runtime.
|
sign the transaction, as required by the Solana runtime.
|
||||||
|
|
||||||
::: details
|
::: details
|
||||||
In future work, we might want to add something like a *Builder* pattern for constructing
|
In future work, we can simplify this example further by using something like a *Builder*
|
||||||
common transactions like creating and then initializing an account.
|
pattern for constructing common transactions like creating and then initializing an account.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
As before, we can run the example tests.
|
As before, we can run the example tests.
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
# Tutorial 2: Account Constraints and Access Control
|
# Tutorial 2: Account Constraints and Access Control
|
||||||
|
|
||||||
Building on the previous two, this tutorial covers how to specify constraints and access control
|
This tutorial covers how to specify constraints and access control on accounts.
|
||||||
on accounts.
|
|
||||||
|
|
||||||
Because Solana programs are stateless, a transaction must specify accounts to be executed. And because an untrusted client specifies those accounts, a program must responsibily validate all input to the program to ensure it is what it claims to be--in addition to any instruction specific access control the program needs to do. This is particularly burdensome when there are lots of dependencies between accounts, leading to repetitive [boilerplate](https://github.com/project-serum/serum-dex/blob/master/registry/src/access_control.rs) code for account validation along with the ability to easily shoot oneself in the foot by forgetting to validate any particular account.
|
Because Solana programs are stateless, a transaction must specify accounts to be executed. And because an untrusted client specifies those accounts, a program must responsibily validate all input to the program to ensure it is what it claims to be--in addition to any instruction specific access control the program needs to do.
|
||||||
|
|
||||||
For example, one could imagine easily writing a faulty token program that forgets to check if the signer of a transaction claiming to be the owner of a token account actually matches the owner on the account. So one must write an `if` statement to check for all such conditions. Instead, one can use the Anchor DSL to do these checks by specifying **constraints** when deriving `Accounts`.
|
For example, one could imagine easily writing a faulty token program that forgets to check if the signer of a transaction claiming to be the owner of a token account actually matches the owner on the account. A simpler question that must be asked: what happens if the program expects a `Mint` account but a `Token` account is given instead?
|
||||||
|
|
||||||
|
|
||||||
|
Doing these checks is particularly burdensome when there are lots of dependencies between
|
||||||
|
accounts, leading to repetitive [boilerplate](https://github.com/project-serum/serum-dex/blob/master/registry/src/access_control.rs)
|
||||||
|
code for account validation along with the ability to easily shoot oneself in the foot.
|
||||||
|
Instead, one can use the Anchor DSL to do these checks by specifying **constraints** when deriving
|
||||||
|
`Accounts`. We briefly touched on the most basic (and important) type of account constraint in the
|
||||||
|
[previous tutorial](./tutorial-1.md), the account discriminator. Here, we demonstrate others.
|
||||||
|
|
||||||
## Clone the Repo
|
## Clone the Repo
|
||||||
|
|
||||||
|
|
|
@ -11,15 +11,27 @@ mod basic_1 {
|
||||||
my_account.data = data;
|
my_account.data = data;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
|
||||||
|
let my_account = &mut ctx.accounts.my_account;
|
||||||
|
my_account.data = data;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct Initialize<'info> {
|
pub struct Initialize<'info> {
|
||||||
|
#[account(init)]
|
||||||
|
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Update<'info> {
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
#[account]
|
||||||
pub struct MyAccount {
|
pub struct MyAccount {
|
||||||
pub data: u64,
|
pub data: u64,
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ describe('basic-1', () => {
|
||||||
anchor.web3.SystemProgram.createAccount({
|
anchor.web3.SystemProgram.createAccount({
|
||||||
fromPubkey: provider.wallet.publicKey,
|
fromPubkey: provider.wallet.publicKey,
|
||||||
newAccountPubkey: myAccount.publicKey,
|
newAccountPubkey: myAccount.publicKey,
|
||||||
space: 8,
|
space: 8+8,
|
||||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(8),
|
lamports: await provider.connection.getMinimumBalanceForRentExemption(8+8),
|
||||||
programId: program.programId,
|
programId: program.programId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -47,6 +47,8 @@ describe('basic-1', () => {
|
||||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reference to an account to use between multiple tests.
|
||||||
|
let _myAccount = undefined;
|
||||||
|
|
||||||
it('Creates and initializes an account in a single atomic transaction', async () => {
|
it('Creates and initializes an account in a single atomic transaction', async () => {
|
||||||
// The program to execute.
|
// The program to execute.
|
||||||
|
@ -66,8 +68,8 @@ describe('basic-1', () => {
|
||||||
anchor.web3.SystemProgram.createAccount({
|
anchor.web3.SystemProgram.createAccount({
|
||||||
fromPubkey: provider.wallet.publicKey,
|
fromPubkey: provider.wallet.publicKey,
|
||||||
newAccountPubkey: myAccount.publicKey,
|
newAccountPubkey: myAccount.publicKey,
|
||||||
space: 8,
|
space: 8+8, // Add 8 for the account discriminator.
|
||||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(8),
|
lamports: await provider.connection.getMinimumBalanceForRentExemption(8+8),
|
||||||
programId: program.programId,
|
programId: program.programId,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -79,5 +81,33 @@ describe('basic-1', () => {
|
||||||
// Check it's state was initialized.
|
// Check it's state was initialized.
|
||||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||||
// #endregion code
|
// #endregion code
|
||||||
|
|
||||||
|
// Store the account for the next test.
|
||||||
|
_myAccount = myAccount;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updates a previously created account', async () => {
|
||||||
|
|
||||||
|
const myAccount = _myAccount;
|
||||||
|
|
||||||
|
// #region update-test
|
||||||
|
|
||||||
|
// The program to execute.
|
||||||
|
const program = anchor.workspace.Basic1;
|
||||||
|
|
||||||
|
// Invoke the update rpc.
|
||||||
|
await program.rpc.update(new anchor.BN(4321), {
|
||||||
|
accounts: {
|
||||||
|
myAccount: myAccount.publicKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch the newly updated account.
|
||||||
|
const account = await program.account.myAccount(myAccount.publicKey);
|
||||||
|
|
||||||
|
// Check it's state was mutated.
|
||||||
|
assert.ok(account.data.eq(new anchor.BN(4321)));
|
||||||
|
|
||||||
|
// #endregion update-test
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,7 +50,7 @@ mod basic_2 {
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct CreateRoot<'info> {
|
pub struct CreateRoot<'info> {
|
||||||
#[account(mut, "!root.initialized")]
|
#[account(init)]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,15 +58,14 @@ pub struct CreateRoot<'info> {
|
||||||
pub struct UpdateRoot<'info> {
|
pub struct UpdateRoot<'info> {
|
||||||
#[account(signer)]
|
#[account(signer)]
|
||||||
pub authority: AccountInfo<'info>,
|
pub authority: AccountInfo<'info>,
|
||||||
#[account(mut, "root.initialized", "&root.authority == authority.key")]
|
#[account(mut, "&root.authority == authority.key")]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct CreateLeaf<'info> {
|
pub struct CreateLeaf<'info> {
|
||||||
#[account("root.initialized")]
|
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
#[account(mut, "!leaf.initialized")]
|
#[account(init)]
|
||||||
pub leaf: ProgramAccount<'info, Leaf>,
|
pub leaf: ProgramAccount<'info, Leaf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,22 +73,22 @@ pub struct CreateLeaf<'info> {
|
||||||
pub struct UpdateLeaf<'info> {
|
pub struct UpdateLeaf<'info> {
|
||||||
#[account(signer)]
|
#[account(signer)]
|
||||||
pub authority: AccountInfo<'info>,
|
pub authority: AccountInfo<'info>,
|
||||||
#[account("root.initialized", "&root.authority == authority.key")]
|
#[account("&root.authority == authority.key")]
|
||||||
pub root: ProgramAccount<'info, Root>,
|
pub root: ProgramAccount<'info, Root>,
|
||||||
#[account(mut, belongs_to = root, "leaf.initialized")]
|
#[account(mut, belongs_to = root)]
|
||||||
pub leaf: ProgramAccount<'info, Leaf>,
|
pub leaf: ProgramAccount<'info, Leaf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the program owned accounts.
|
// Define the program owned accounts.
|
||||||
|
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
#[account]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
pub data: u64,
|
pub data: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
#[account]
|
||||||
pub struct Leaf {
|
pub struct Leaf {
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
pub root: Pubkey,
|
pub root: Pubkey,
|
||||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -1,25 +1,99 @@
|
||||||
use solana_sdk::account_info::AccountInfo;
|
use solana_sdk::account_info::AccountInfo;
|
||||||
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::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub use anchor_attributes_access_control::access_control;
|
pub use anchor_attribute_access_control::access_control;
|
||||||
pub use anchor_attributes_program::program;
|
pub use anchor_attribute_account::account;
|
||||||
pub use anchor_derive::Accounts;
|
pub use anchor_attribute_program::program;
|
||||||
|
pub use anchor_derive_accounts::Accounts;
|
||||||
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
|
||||||
|
|
||||||
pub struct ProgramAccount<'a, T: AnchorSerialize + AnchorDeserialize> {
|
/// A data structure of Solana 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
|
||||||
|
/// the accounts maintain any invariants required for the program to run
|
||||||
|
/// securely.
|
||||||
|
pub trait Accounts<'info>: Sized {
|
||||||
|
fn try_accounts(program_id: &Pubkey, from: &[AccountInfo<'info>])
|
||||||
|
-> Result<Self, ProgramError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data structure that can be serialized and stored in an `AccountInfo` data
|
||||||
|
/// array.
|
||||||
|
///
|
||||||
|
/// Implementors of this trait should ensure that any subsequent usage the
|
||||||
|
/// `AccountDeserialize` trait succeeds if and only if the account is of the
|
||||||
|
/// correct type. For example, the implementation provided by the `#[account]`
|
||||||
|
/// attribute sets the first 8 bytes to be a unique account discriminator,
|
||||||
|
/// defined as the first 8 bytes of the SHA256 of the account's Rust ident.
|
||||||
|
/// Thus, any subsequent calls via `AccountDeserialize`'s `try_deserialize`
|
||||||
|
/// will check this discriminator. If it doesn't match, an invalid account
|
||||||
|
/// was given, and the program will exit with an error.
|
||||||
|
pub trait AccountSerialize {
|
||||||
|
/// Serilalizes the account data into `writer`.
|
||||||
|
fn try_serialize<W: Write>(&self, writer: &mut W) -> Result<(), ProgramError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data structure that can be deserialized from an `AccountInfo` data array.
|
||||||
|
pub trait AccountDeserialize: Sized {
|
||||||
|
/// Deserializes the account data.
|
||||||
|
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
||||||
|
|
||||||
|
/// Deserializes account data without checking the account discriminator.
|
||||||
|
/// This should only be used on account initialization, when the
|
||||||
|
/// discriminator is not yet set (since the entire account data is zeroed).
|
||||||
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A container for a deserialized `account` and raw `AccountInfo` object.
|
||||||
|
///
|
||||||
|
/// Using this within a data structure deriving `Accounts` will ensure the
|
||||||
|
/// account is owned by the currently executing program.
|
||||||
|
pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize> {
|
||||||
pub info: AccountInfo<'a>,
|
pub info: AccountInfo<'a>,
|
||||||
pub account: T,
|
pub account: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: AnchorSerialize + AnchorDeserialize> ProgramAccount<'a, T> {
|
impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
|
||||||
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
|
pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
|
||||||
Self { info, account }
|
Self { info, account }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T> {
|
impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a, T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -27,16 +101,15 @@ impl<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: AnchorSerialize + AnchorDeserialize> DerefMut for ProgramAccount<'a, T> {
|
impl<'a, T: AccountSerialize + AccountDeserialize> DerefMut for ProgramAccount<'a, T> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.account
|
&mut self.account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Accounts<'info>: Sized {
|
/// A data structure providing non-argument inputs to the Solana program, namely
|
||||||
fn try_anchor(program_id: &Pubkey, from: &[AccountInfo<'info>]) -> Result<Self, ProgramError>;
|
/// the currently executing program's ID and the set of validated, deserialized
|
||||||
}
|
/// accounts.
|
||||||
|
|
||||||
pub struct Context<'a, 'b, T> {
|
pub struct Context<'a, 'b, T> {
|
||||||
pub accounts: &'a mut T,
|
pub accounts: &'a mut T,
|
||||||
pub program_id: &'b Pubkey,
|
pub program_id: &'b Pubkey,
|
||||||
|
@ -44,8 +117,8 @@ pub struct Context<'a, 'b, T> {
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
access_control, program, Accounts, AnchorDeserialize, AnchorSerialize, Context,
|
access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
|
||||||
ProgramAccount,
|
AnchorDeserialize, AnchorSerialize, Context, ProgramAccount,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use solana_program::msg;
|
pub use solana_program::msg;
|
||||||
|
|
|
@ -52,8 +52,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let mut data = self.#info.try_borrow_mut_data()?;
|
let mut data = self.#info.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
let dst: &mut [u8] = &mut data;
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
self.#ident.account.serialize(&mut cursor)
|
self.#ident.account.try_serialize(&mut cursor)?;
|
||||||
.map_err(|_| ProgramError::InvalidAccountData)?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -70,7 +69,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_anchor(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
fn try_accounts(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||||
let acc_infos = &mut accounts.iter();
|
let acc_infos = &mut accounts.iter();
|
||||||
|
|
||||||
#(#acc_infos)*
|
#(#acc_infos)*
|
||||||
|
@ -92,13 +91,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpacks the field, if needed.
|
|
||||||
pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
|
pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
|
||||||
let checks: Vec<proc_macro2::TokenStream> = f
|
|
||||||
.constraints
|
|
||||||
.iter()
|
|
||||||
.map(|c| generate_constraint(&f, c))
|
|
||||||
.collect();
|
|
||||||
let ident = &f.ident;
|
let ident = &f.ident;
|
||||||
let assign_ty = match &f.ty {
|
let assign_ty = match &f.ty {
|
||||||
Ty::AccountInfo => quote! {
|
Ty::AccountInfo => quote! {
|
||||||
|
@ -106,16 +99,21 @@ pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
|
||||||
},
|
},
|
||||||
Ty::ProgramAccount(acc) => {
|
Ty::ProgramAccount(acc) => {
|
||||||
let account_struct = &acc.account_ident;
|
let account_struct = &acc.account_ident;
|
||||||
quote! {
|
match f.is_init {
|
||||||
let mut data: &[u8] = &#ident.try_borrow_data()?;
|
false => quote! {
|
||||||
let #ident = ProgramAccount::new(
|
let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from(#ident)?;
|
||||||
#ident.clone(),
|
},
|
||||||
#account_struct::deserialize(&mut data)
|
true => quote! {
|
||||||
.map_err(|_| ProgramError::InvalidAccountData)?
|
let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from_init(#ident)?;
|
||||||
);
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let checks: Vec<proc_macro2::TokenStream> = f
|
||||||
|
.constraints
|
||||||
|
.iter()
|
||||||
|
.map(|c| generate_constraint(&f, c))
|
||||||
|
.collect();
|
||||||
quote! {
|
quote! {
|
||||||
#assign_ty
|
#assign_ty
|
||||||
#(#checks)*
|
#(#checks)*
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub fn generate() {
|
|
||||||
// todo
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
||||||
let instruction = generate_instruction(&program);
|
let instruction = generate_instruction(&program);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
// Import everything in the mod, in case the user wants to put anchors
|
// Import everything in the mod, in case the user wants to put types
|
||||||
// in there.
|
// in there.
|
||||||
use #mod_name::*;
|
use #mod_name::*;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
instruction::#variant_arm => {
|
instruction::#variant_arm => {
|
||||||
let mut accounts = #anchor::try_anchor(program_id, accounts)?;
|
let mut accounts = #anchor::try_accounts(program_id, accounts)?;
|
||||||
#program_name::#rpc_name(
|
#program_name::#rpc_name(
|
||||||
Context {
|
Context {
|
||||||
accounts: &mut accounts,
|
accounts: &mut accounts,
|
||||||
|
|
|
@ -64,6 +64,7 @@ pub struct Field {
|
||||||
pub constraints: Vec<Constraint>,
|
pub constraints: Vec<Constraint>,
|
||||||
pub is_mut: bool,
|
pub is_mut: bool,
|
||||||
pub is_signer: bool,
|
pub is_signer: bool,
|
||||||
|
pub is_init: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A type of an account field.
|
// A type of an account field.
|
||||||
|
|
|
@ -27,8 +27,11 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
Some(attr)
|
Some(attr)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
assert!(anchor_attrs.len() == 1);
|
match anchor_attrs.len() {
|
||||||
anchor_attrs[0]
|
0 => None,
|
||||||
|
1 => Some(anchor_attrs[0]),
|
||||||
|
_ => panic!("invalid syntax: only one account attribute is allowed"),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
parse_field(f, anchor_attr)
|
parse_field(f, anchor_attr)
|
||||||
})
|
})
|
||||||
|
@ -38,16 +41,20 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses an inert #[anchor] attribute specifying the DSL.
|
// Parses an inert #[anchor] attribute specifying the DSL.
|
||||||
fn parse_field(f: &syn::Field, anchor: &syn::Attribute) -> Field {
|
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);
|
let ty = parse_ty(f);
|
||||||
let (constraints, is_mut, is_signer) = parse_constraints(anchor, &ty);
|
let (constraints, is_mut, is_signer, is_init) = match anchor {
|
||||||
|
None => (vec![], false, false, false),
|
||||||
|
Some(anchor) => parse_constraints(anchor, &ty),
|
||||||
|
};
|
||||||
Field {
|
Field {
|
||||||
ident,
|
ident,
|
||||||
ty,
|
ty,
|
||||||
constraints,
|
constraints,
|
||||||
is_mut,
|
is_mut,
|
||||||
is_signer,
|
is_signer,
|
||||||
|
is_init,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,13 +97,14 @@ fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
|
||||||
ProgramAccountTy { account_ident }
|
ProgramAccountTy { account_ident }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool) {
|
fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool, bool) {
|
||||||
let mut tts = anchor.tokens.clone().into_iter();
|
let mut tts = anchor.tokens.clone().into_iter();
|
||||||
let g_stream = match tts.next().expect("Must have a token group") {
|
let g_stream = match tts.next().expect("Must have a token group") {
|
||||||
proc_macro2::TokenTree::Group(g) => g.stream(),
|
proc_macro2::TokenTree::Group(g) => g.stream(),
|
||||||
_ => panic!("Invalid syntax"),
|
_ => panic!("Invalid syntax"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut is_init = false;
|
||||||
let mut is_mut = false;
|
let mut is_mut = false;
|
||||||
let mut is_signer = false;
|
let mut is_signer = false;
|
||||||
let mut constraints = vec![];
|
let mut constraints = vec![];
|
||||||
|
@ -106,6 +114,10 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
|
||||||
while let Some(token) = inner_tts.next() {
|
while let Some(token) = inner_tts.next() {
|
||||||
match token {
|
match token {
|
||||||
proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
|
proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
|
||||||
|
"init" => {
|
||||||
|
is_init = true;
|
||||||
|
is_mut = true;
|
||||||
|
}
|
||||||
"mut" => {
|
"mut" => {
|
||||||
is_mut = true;
|
is_mut = true;
|
||||||
}
|
}
|
||||||
|
@ -175,5 +187,5 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(constraints, is_mut, is_signer)
|
(constraints, is_mut, is_signer, is_init)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@project-serum/anchor",
|
"name": "@project-serum/anchor",
|
||||||
"version": "0.0.0-alpha.1",
|
"version": "0.0.0-alpha.2",
|
||||||
"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",
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
"bn.js": "^5.1.2",
|
"bn.js": "^5.1.2",
|
||||||
"buffer-layout": "^1.2.0",
|
"buffer-layout": "^1.2.0",
|
||||||
"camelcase": "^5.3.1",
|
"camelcase": "^5.3.1",
|
||||||
|
"crypto-hash": "^1.3.0",
|
||||||
"find": "^0.3.0"
|
"find": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
import { sha256 } from "crypto-hash";
|
||||||
import { Idl, IdlInstruction } from "./idl";
|
import { Idl, IdlInstruction } from "./idl";
|
||||||
import { IdlError } from "./error";
|
import { IdlError } from "./error";
|
||||||
import Coder from "./coder";
|
import Coder from "./coder";
|
||||||
|
@ -35,24 +36,24 @@ export interface Accounts {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RpcFn is a single rpc method.
|
* RpcFn is a single rpc method generated from an IDL.
|
||||||
*/
|
*/
|
||||||
export type RpcFn = (...args: any[]) => Promise<any>;
|
export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ix is a function to create a `TransactionInstruction`.
|
* Ix is a function to create a `TransactionInstruction` generated from an IDL.
|
||||||
*/
|
*/
|
||||||
export type IxFn = (...args: any[]) => TransactionInstruction;
|
export type IxFn = (...args: any[]) => TransactionInstruction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account is a function returning a deserialized account, given an address.
|
* Account is a function returning a deserialized account, given an address.
|
||||||
*/
|
*/
|
||||||
export type AccountFn = (address: PublicKey) => any;
|
export type AccountFn<T = any> = (address: PublicKey) => T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for an RPC invocation.
|
* Options for an RPC invocation.
|
||||||
*/
|
*/
|
||||||
type RpcOptions = ConfirmOptions;
|
export type RpcOptions = ConfirmOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RpcContext provides all arguments for an RPC/IX invocation that are not
|
* RpcContext provides all arguments for an RPC/IX invocation that are not
|
||||||
|
@ -107,7 +108,6 @@ export class RpcFactory {
|
||||||
|
|
||||||
if (idl.accounts) {
|
if (idl.accounts) {
|
||||||
idl.accounts.forEach((idlAccount) => {
|
idl.accounts.forEach((idlAccount) => {
|
||||||
// todo
|
|
||||||
const accountFn = async (address: PublicKey): Promise<any> => {
|
const accountFn = async (address: PublicKey): Promise<any> => {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
if (provider === null) {
|
if (provider === null) {
|
||||||
|
@ -117,7 +117,24 @@ export class RpcFactory {
|
||||||
if (accountInfo === null) {
|
if (accountInfo === null) {
|
||||||
throw new Error(`Entity does not exist ${address}`);
|
throw new Error(`Entity does not exist ${address}`);
|
||||||
}
|
}
|
||||||
return coder.accounts.decode(idlAccount.name, accountInfo.data);
|
|
||||||
|
// Assert the account discriminator is correct.
|
||||||
|
const expectedDiscriminator = Buffer.from(
|
||||||
|
(
|
||||||
|
await sha256(`account:${idlAccount.name}`, {
|
||||||
|
outputFormat: "buffer",
|
||||||
|
})
|
||||||
|
).slice(0, 8)
|
||||||
|
);
|
||||||
|
const discriminator = accountInfo.data.slice(0, 8);
|
||||||
|
|
||||||
|
if (expectedDiscriminator.compare(discriminator)) {
|
||||||
|
throw new Error("Invalid account discriminator");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chop off the discriminator before decoding.
|
||||||
|
const data = accountInfo.data.slice(8);
|
||||||
|
return coder.accounts.decode(idlAccount.name, data);
|
||||||
};
|
};
|
||||||
const name = camelCase(idlAccount.name);
|
const name = camelCase(idlAccount.name);
|
||||||
accountFns[name] = accountFn;
|
accountFns[name] = accountFn;
|
||||||
|
|
|
@ -1800,7 +1800,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
crypto-hash@^1.2.2:
|
crypto-hash@^1.2.2, crypto-hash@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247"
|
resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247"
|
||||||
integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==
|
integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==
|
||||||
|
|
Loading…
Reference in New Issue