Merge pull request #41 from project-serum/armani/stateix
This commit is contained in:
commit
b87def6f8c
84
README.md
84
README.md
|
@ -8,7 +8,7 @@
|
|||
|
||||
Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime providing several convenient developer tools.
|
||||
|
||||
- Rust DSL for writing Solana programs
|
||||
- Rust eDSL for writing 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
|
||||
|
@ -25,7 +25,55 @@ To jump straight to examples, go [here](https://github.com/project-serum/anchor/
|
|||
* **Anchor is in active development, so all APIs are subject to change.**
|
||||
* **This code is unaudited. Use at your own risk.**
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
Build stateful programs on Solana by defining a state struct with associated
|
||||
methods. Here's a classic counter example, where only the designated `authority`
|
||||
can increment the count.
|
||||
|
||||
```rust
|
||||
#[program]
|
||||
mod counter {
|
||||
|
||||
#[state]
|
||||
pub struct Counter {
|
||||
authority: Pubkey,
|
||||
count: u64,
|
||||
}
|
||||
|
||||
pub fn new(ctx: Context<Auth>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
auth: *auth.accounts.authority.key
|
||||
})
|
||||
}
|
||||
|
||||
pub fn increment(&mut self, ctx: Context<Auth>) -> Result<()> {
|
||||
if &self.authority != ctx.accounts.authority.key {
|
||||
return Err(ErrorCode::Unauthorized.into());
|
||||
}
|
||||
|
||||
self.count += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Auth<'info> {
|
||||
#[account(signer)]
|
||||
authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("You are not authorized to perform this action.")]
|
||||
Unauthorized,
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, one can utilize the full power of Solana's parallel execution model by
|
||||
keeping the program stateless and working with accounts directly. The above example
|
||||
can be rewritten as follows.
|
||||
|
||||
```rust
|
||||
use anchor::prelude::*;
|
||||
|
@ -33,18 +81,23 @@ use anchor::prelude::*;
|
|||
// Define instruction handlers.
|
||||
|
||||
#[program]
|
||||
mod example {
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> ProgramResult {
|
||||
let my_account = &mut ctx.accounts.my_account;
|
||||
my_account.authority = authority;
|
||||
let counter = &mut ctx.accounts.counter;
|
||||
|
||||
counter.authority = authority;
|
||||
counter.count = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
|
||||
let my_account = &mut ctx.accounts.my_account;
|
||||
my_account.data = data;
|
||||
pub fn increment(ctx: Context<Update>) -> ProgramResult {
|
||||
let counter = &mut ctx.accounts.counter;
|
||||
|
||||
counter += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +107,14 @@ mod example {
|
|||
#[derive(Accounts)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(init)]
|
||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||
pub counter: ProgramAccount<'info, Counter>,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Update<'info> {
|
||||
pub struct Increment<'info> {
|
||||
#[account(mut, has_one = authority)]
|
||||
pub my_account: ProgramAccount<'info, MyAccount>,
|
||||
pub counter: ProgramAccount<'info, Counter>,
|
||||
#[account(signer)]
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
@ -69,12 +122,17 @@ pub struct Update<'info> {
|
|||
// Define program owned accounts.
|
||||
|
||||
#[account]
|
||||
pub struct MyAccount {
|
||||
pub struct Counter {
|
||||
pub authority: Pubkey,
|
||||
pub data: u64,
|
||||
pub count: u64,
|
||||
}
|
||||
```
|
||||
|
||||
Due to the fact that account sizes on Solana are fixed, some combination of
|
||||
the above is often required. For example, one can store store global state
|
||||
associated with the entire program in the `#[state]` struct and local
|
||||
state assocated with each user in individual `#[account]` structs.
|
||||
|
||||
## Accounts attribute syntax.
|
||||
|
||||
There are several inert attributes (attributes that are consumed only for the
|
||||
|
|
|
@ -25,7 +25,7 @@ pub fn account(
|
|||
|
||||
let coder = quote! {
|
||||
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) -> std::result::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.
|
||||
|
@ -48,7 +48,7 @@ pub fn account(
|
|||
|
||||
impl anchor_lang::AccountDeserialize for #account_name {
|
||||
|
||||
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
||||
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
||||
let mut discriminator = [0u8; 8];
|
||||
discriminator.copy_from_slice(
|
||||
&anchor_lang::solana_program::hash::hash(
|
||||
|
@ -66,7 +66,7 @@ pub fn account(
|
|||
Self::try_deserialize_unchecked(buf)
|
||||
}
|
||||
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
||||
let mut data: &[u8] = &buf[8..];
|
||||
AnchorDeserialize::deserialize(&mut data)
|
||||
.map_err(|_| ProgramError::InvalidAccountData)
|
||||
|
|
|
@ -15,3 +15,11 @@ pub fn state(
|
|||
#item_struct
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
impl<'a, 'b, 'c, 'info> anchor_lang::ProgramStateContext<'a, 'b, 'c, 'info> for #strct {
|
||||
fn context() -> Context<'a, 'b, 'c, 'info, ProgramStateAccounts<'info>> {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -47,7 +47,7 @@ pub fn lib_rs(name: &str) -> String {
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
#[program]
|
||||
mod {} {{
|
||||
pub mod {} {{
|
||||
use super::*;
|
||||
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {{
|
||||
Ok(())
|
||||
|
|
|
@ -10,8 +10,10 @@ use anchor_spl::token::{self, TokenAccount, Transfer};
|
|||
|
||||
mod calculator;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[program]
|
||||
mod lockup {
|
||||
pub mod lockup {
|
||||
use super::*;
|
||||
|
||||
#[state]
|
||||
|
@ -25,20 +27,45 @@ mod lockup {
|
|||
impl Lockup {
|
||||
pub const WHITELIST_SIZE: usize = 5;
|
||||
|
||||
pub fn new(authority: Pubkey) -> Result<Self, Error> {
|
||||
pub fn new(ctx: Context<Auth>) -> Result<Self> {
|
||||
let mut whitelist = vec![];
|
||||
whitelist.resize(Self::WHITELIST_SIZE, Default::default());
|
||||
Ok(Lockup {
|
||||
authority,
|
||||
authority: *ctx.accounts.authority.key,
|
||||
whitelist,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_authority(ctx: Context<SetAuthority>, new_authority: Pubkey) -> Result<(), Error> {
|
||||
let lockup = &mut ctx.accounts.lockup;
|
||||
lockup.authority = new_authority;
|
||||
Ok(())
|
||||
#[access_control(whitelist_auth(self, &ctx))]
|
||||
pub fn whitelist_add(&mut self, ctx: Context<Auth>, entry: WhitelistEntry) -> Result<()> {
|
||||
if self.whitelist.len() == Self::WHITELIST_SIZE {
|
||||
return Err(ErrorCode::WhitelistFull.into());
|
||||
}
|
||||
if self.whitelist.contains(&entry) {
|
||||
return Err(ErrorCode::WhitelistEntryAlreadyExists.into());
|
||||
}
|
||||
self.whitelist.push(entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(whitelist_auth(self, &ctx))]
|
||||
pub fn whitelist_delete(
|
||||
&mut self,
|
||||
ctx: Context<Auth>,
|
||||
entry: WhitelistEntry,
|
||||
) -> Result<()> {
|
||||
if !self.whitelist.contains(&entry) {
|
||||
return Err(ErrorCode::WhitelistEntryNotFound.into());
|
||||
}
|
||||
self.whitelist.retain(|e| e != &entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(whitelist_auth(self, &ctx))]
|
||||
pub fn set_authority(&mut self, ctx: Context<Auth>, new_authority: Pubkey) -> Result<()> {
|
||||
self.authority = new_authority;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[access_control(CreateVesting::accounts(&ctx, nonce))]
|
||||
|
@ -49,7 +76,7 @@ mod lockup {
|
|||
period_count: u64,
|
||||
deposit_amount: u64,
|
||||
nonce: u8,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
if end_ts <= ctx.accounts.clock.unix_timestamp {
|
||||
return Err(ErrorCode::InvalidTimestamp.into());
|
||||
}
|
||||
|
@ -78,7 +105,7 @@ mod lockup {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<(), Error> {
|
||||
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
|
||||
// Has the given amount vested?
|
||||
if amount
|
||||
> calculator::available_for_withdrawal(
|
||||
|
@ -105,32 +132,12 @@ mod lockup {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(whitelist_has_capacity(&ctx))]
|
||||
pub fn whitelist_add(ctx: Context<WhitelistAdd>, entry: WhitelistEntry) -> Result<(), Error> {
|
||||
if ctx.accounts.lockup.whitelist.contains(&entry) {
|
||||
return Err(ErrorCode::WhitelistEntryAlreadyExists.into());
|
||||
}
|
||||
ctx.accounts.lockup.whitelist.push(entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn whitelist_delete(
|
||||
ctx: Context<WhitelistAdd>,
|
||||
entry: WhitelistEntry,
|
||||
) -> Result<(), Error> {
|
||||
if !ctx.accounts.lockup.whitelist.contains(&entry) {
|
||||
return Err(ErrorCode::WhitelistEntryNotFound.into());
|
||||
}
|
||||
ctx.accounts.lockup.whitelist.retain(|e| e != &entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Sends funds from the lockup program to a whitelisted program.
|
||||
pub fn whitelist_withdraw(
|
||||
ctx: Context<WhitelistWithdraw>,
|
||||
instruction_data: Vec<u8>,
|
||||
amount: u64,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
let before_amount = ctx.accounts.transfer.vault.amount;
|
||||
whitelist_relay_cpi(
|
||||
&ctx.accounts.transfer,
|
||||
|
@ -155,7 +162,7 @@ mod lockup {
|
|||
pub fn whitelist_deposit(
|
||||
ctx: Context<WhitelistDeposit>,
|
||||
instruction_data: Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
let before_amount = ctx.accounts.transfer.vault.amount;
|
||||
whitelist_relay_cpi(
|
||||
&ctx.accounts.transfer,
|
||||
|
@ -180,7 +187,7 @@ mod lockup {
|
|||
}
|
||||
|
||||
// Convenience function for UI's to calculate the withdrawalable amount.
|
||||
pub fn available_for_withdrawal(ctx: Context<AvailableForWithdrawal>) -> Result<(), Error> {
|
||||
pub fn available_for_withdrawal(ctx: Context<AvailableForWithdrawal>) -> Result<()> {
|
||||
let available = calculator::available_for_withdrawal(
|
||||
&ctx.accounts.vesting,
|
||||
ctx.accounts.clock.unix_timestamp,
|
||||
|
@ -192,9 +199,7 @@ mod lockup {
|
|||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetAuthority<'info> {
|
||||
#[account(mut, has_one = authority)]
|
||||
lockup: ProgramState<'info, Lockup>,
|
||||
pub struct Auth<'info> {
|
||||
#[account(signer)]
|
||||
authority: AccountInfo<'info>,
|
||||
}
|
||||
|
@ -219,7 +224,7 @@ pub struct CreateVesting<'info> {
|
|||
}
|
||||
|
||||
impl<'info> CreateVesting<'info> {
|
||||
fn accounts(ctx: &Context<CreateVesting>, nonce: u8) -> Result<(), Error> {
|
||||
fn accounts(ctx: &Context<CreateVesting>, nonce: u8) -> Result<()> {
|
||||
let vault_authority = Pubkey::create_program_address(
|
||||
&[
|
||||
ctx.accounts.vesting.to_account_info().key.as_ref(),
|
||||
|
@ -256,22 +261,6 @@ pub struct Withdraw<'info> {
|
|||
clock: Sysvar<'info, Clock>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct WhitelistAdd<'info> {
|
||||
#[account(mut, has_one = authority)]
|
||||
lockup: ProgramState<'info, Lockup>,
|
||||
#[account(signer)]
|
||||
authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct WhitelistDelete<'info> {
|
||||
#[account(mut, has_one = authority)]
|
||||
lockup: ProgramState<'info, Lockup>,
|
||||
#[account(signer)]
|
||||
authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct WhitelistWithdraw<'info> {
|
||||
transfer: WhitelistTransfer<'info>,
|
||||
|
@ -374,6 +363,8 @@ pub enum ErrorCode {
|
|||
WhitelistWithdrawLimit,
|
||||
#[msg("Whitelist entry not found.")]
|
||||
WhitelistEntryNotFound,
|
||||
#[msg("You do not have sufficient permissions to perform this action.")]
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info> From<&mut CreateVesting<'info>>
|
||||
|
@ -402,19 +393,12 @@ impl<'a, 'b, 'c, 'info> From<&Withdraw<'info>> for CpiContext<'a, 'b, 'c, 'info,
|
|||
}
|
||||
}
|
||||
|
||||
fn whitelist_has_capacity(ctx: &Context<WhitelistAdd>) -> Result<(), Error> {
|
||||
if ctx.accounts.lockup.whitelist.len() == lockup::Lockup::WHITELIST_SIZE {
|
||||
return Err(ErrorCode::WhitelistFull.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(is_whitelisted(transfer))]
|
||||
pub fn whitelist_relay_cpi<'info>(
|
||||
transfer: &WhitelistTransfer,
|
||||
remaining_accounts: &[AccountInfo<'info>],
|
||||
instruction_data: Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
let mut meta_accounts = vec![
|
||||
AccountMeta::new_readonly(*transfer.vesting.to_account_info().key, false),
|
||||
AccountMeta::new(*transfer.vault.to_account_info().key, false),
|
||||
|
@ -454,7 +438,7 @@ pub fn whitelist_relay_cpi<'info>(
|
|||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<(), Error> {
|
||||
pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()> {
|
||||
if !transfer.lockup.whitelist.contains(&WhitelistEntry {
|
||||
program_id: *transfer.whitelisted_program.key,
|
||||
}) {
|
||||
|
@ -462,3 +446,10 @@ pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<(),
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn whitelist_auth(lockup: &Lockup, ctx: &Context<Auth>) -> Result<()> {
|
||||
if &lockup.authority != ctx.accounts.authority.key {
|
||||
return Err(ErrorCode::Unauthorized.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -19,8 +19,10 @@ mod registry {
|
|||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn new<'info>(lockup_program: Pubkey) -> Result<Self, Error> {
|
||||
Ok(Registry { lockup_program })
|
||||
pub fn new<'info>(ctx: Context<Ctor>) -> Result<Self, Error> {
|
||||
Ok(Registry {
|
||||
lockup_program: *ctx.accounts.lockup_program.key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -598,6 +600,11 @@ pub struct BalanceSandboxAccounts<'info> {
|
|||
vault_pw: CpiAccount<'info, TokenAccount>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Ctor<'info> {
|
||||
lockup_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct UpdateMember<'info> {
|
||||
#[account(mut, has_one = beneficiary)]
|
||||
|
|
|
@ -28,7 +28,11 @@ describe("Lockup and Registry", () => {
|
|||
});
|
||||
|
||||
it("Is initialized!", async () => {
|
||||
await lockup.state.rpc.new(provider.wallet.publicKey);
|
||||
await lockup.state.rpc.new({
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
|
||||
lockupAddress = await lockup.state.address();
|
||||
const lockupAccount = await lockup.state();
|
||||
|
@ -42,30 +46,27 @@ describe("Lockup and Registry", () => {
|
|||
|
||||
it("Deletes the default whitelisted addresses", async () => {
|
||||
const defaultEntry = { programId: new anchor.web3.PublicKey() };
|
||||
await lockup.rpc.whitelistDelete(defaultEntry, {
|
||||
await lockup.state.rpc.whitelistDelete(defaultEntry, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
lockup: lockupAddress,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Sets a new authority", async () => {
|
||||
const newAuthority = new anchor.web3.Account();
|
||||
await lockup.rpc.setAuthority(newAuthority.publicKey, {
|
||||
await lockup.state.rpc.setAuthority(newAuthority.publicKey, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
lockup: lockupAddress,
|
||||
},
|
||||
});
|
||||
|
||||
let lockupAccount = await lockup.state();
|
||||
assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));
|
||||
|
||||
await lockup.rpc.setAuthority(provider.wallet.publicKey, {
|
||||
await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
|
||||
accounts: {
|
||||
authority: newAuthority.publicKey,
|
||||
lockup: lockupAddress,
|
||||
},
|
||||
signers: [newAuthority],
|
||||
});
|
||||
|
@ -96,20 +97,19 @@ describe("Lockup and Registry", () => {
|
|||
|
||||
const accounts = {
|
||||
authority: provider.wallet.publicKey,
|
||||
lockup: lockupAddress,
|
||||
};
|
||||
|
||||
await lockup.rpc.whitelistAdd(e0, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e0, { accounts });
|
||||
|
||||
let lockupAccount = await lockup.state();
|
||||
|
||||
assert.ok(lockupAccount.whitelist.length === 1);
|
||||
assert.deepEqual(lockupAccount.whitelist, [e0]);
|
||||
|
||||
await lockup.rpc.whitelistAdd(e1, { accounts });
|
||||
await lockup.rpc.whitelistAdd(e2, { accounts });
|
||||
await lockup.rpc.whitelistAdd(e3, { accounts });
|
||||
await lockup.rpc.whitelistAdd(e4, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e1, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e2, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e3, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e4, { accounts });
|
||||
|
||||
lockupAccount = await lockup.state();
|
||||
|
||||
|
@ -117,7 +117,7 @@ describe("Lockup and Registry", () => {
|
|||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await lockup.rpc.whitelistAdd(e5, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e5, { accounts });
|
||||
},
|
||||
(err) => {
|
||||
assert.equal(err.code, 108);
|
||||
|
@ -128,10 +128,9 @@ describe("Lockup and Registry", () => {
|
|||
});
|
||||
|
||||
it("Removes from the whitelist", async () => {
|
||||
await lockup.rpc.whitelistDelete(e0, {
|
||||
await lockup.state.rpc.whitelistDelete(e0, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
lockup: lockupAddress,
|
||||
},
|
||||
});
|
||||
let lockupAccount = await lockup.state();
|
||||
|
@ -284,7 +283,9 @@ describe("Lockup and Registry", () => {
|
|||
});
|
||||
|
||||
it("Initializes registry's global state", async () => {
|
||||
await registry.state.rpc.new(lockup.programId);
|
||||
await registry.state.rpc.new({
|
||||
accounts: { lockupProgram: lockup.programId },
|
||||
});
|
||||
|
||||
const state = await registry.state();
|
||||
assert.ok(state.lockupProgram.equals(lockup.programId));
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
#[program]
|
||||
mod basic_4 {
|
||||
pub mod basic_4 {
|
||||
use super::*;
|
||||
|
||||
#[state]
|
||||
|
@ -13,9 +13,12 @@ mod basic_4 {
|
|||
}
|
||||
|
||||
impl MyProgram {
|
||||
pub fn new(data: u64) -> Result<Self, ProgramError> {
|
||||
pub fn new(ctx: Context<Ctor>, data: u64) -> Result<Self, ProgramError> {
|
||||
Ok(Self { data })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Ctor {}
|
||||
// #endregion code
|
||||
|
|
|
@ -41,7 +41,7 @@ pub use crate::context::{Context, CpiContext};
|
|||
pub use crate::cpi_account::CpiAccount;
|
||||
pub use crate::ctor::Ctor;
|
||||
pub use crate::program_account::ProgramAccount;
|
||||
pub use crate::state::ProgramState;
|
||||
pub use crate::state::{ProgramState, ProgramStateAccounts};
|
||||
pub use crate::sysvar::Sysvar;
|
||||
pub use anchor_attribute_access_control::access_control;
|
||||
pub use anchor_attribute_account::account;
|
||||
|
|
18
src/state.rs
18
src/state.rs
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiAccount, ToAccountInfo,
|
||||
ToAccountInfos, ToAccountMetas,
|
||||
self as anchor_lang, AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Context,
|
||||
CpiAccount, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
|
@ -9,6 +9,20 @@ use solana_program::program_error::ProgramError;
|
|||
use solana_program::pubkey::Pubkey;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Trait implemented by program state structs, providing the execution context
|
||||
/// for the currently executing program.
|
||||
pub trait ProgramStateContext<'a, 'b, 'c, 'info> {
|
||||
fn context() -> Context<'a, 'b, 'c, 'info, ProgramStateAccounts<'info>>;
|
||||
}
|
||||
|
||||
/// Accounts given to instructions defined on a state struct, excluding the
|
||||
/// state account itself.
|
||||
#[derive(Accounts)]
|
||||
pub struct ProgramStateAccounts<'info> {
|
||||
#[account(signer)]
|
||||
pub payer: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
/// Boxed container for the program state singleton.
|
||||
#[derive(Clone)]
|
||||
pub struct ProgramState<'info, T: AccountSerialize + AccountDeserialize + Clone> {
|
||||
|
|
|
@ -38,7 +38,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
|
|||
}
|
||||
|
||||
impl<'info, T: solana_program::sysvar::Sysvar> ToAccountMetas for Sysvar<'info, T> {
|
||||
fn to_account_metas(&self, is_mut_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
fn to_account_metas(&self, _is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
vec![AccountMeta::new_readonly(*self.info.key, false)]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
quote! {
|
||||
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
||||
#[inline(never)]
|
||||
fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
||||
fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
||||
// Deserialize each account.
|
||||
#(#deser_fields)*
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
|
|||
#error_enum
|
||||
|
||||
impl std::fmt::Display for #enum_name {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
<Self as std::fmt::Debug>::fmt(self, fmt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::parser;
|
||||
use crate::{Program, Rpc, State};
|
||||
use crate::{Program, RpcArg, State};
|
||||
use heck::CamelCase;
|
||||
use quote::quote;
|
||||
|
||||
|
@ -42,6 +42,7 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
#cpi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
||||
let ctor_state_dispatch_arm = match &program.state {
|
||||
None => quote! { /* no-op */ },
|
||||
|
@ -49,21 +50,33 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
let variant_arm = generate_ctor_variant(program, state);
|
||||
let ctor_args = generate_ctor_args(state);
|
||||
quote! {
|
||||
__private::instruction::#variant_arm => {
|
||||
__private::__ctor(program_id, accounts, #(#ctor_args),*)
|
||||
}
|
||||
__private::instruction::#variant_arm => __private::__ctor(program_id, accounts, #(#ctor_args),*),
|
||||
}
|
||||
}
|
||||
};
|
||||
let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
|
||||
None => vec![quote! { /* no-op */}],
|
||||
None => vec![],
|
||||
Some(s) => s
|
||||
.methods
|
||||
.iter()
|
||||
.map(|m| {
|
||||
.map(|rpc: &crate::StateRpc| {
|
||||
let rpc_arg_names: Vec<&syn::Ident> =
|
||||
rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
let variant_arm: proc_macro2::TokenStream = generate_ix_variant(
|
||||
program,
|
||||
rpc.raw_method.sig.ident.to_string(),
|
||||
&rpc.args,
|
||||
true,
|
||||
);
|
||||
let rpc_name: proc_macro2::TokenStream = {
|
||||
let name = &rpc.raw_method.sig.ident.to_string();
|
||||
format!("__{}", name).parse().unwrap()
|
||||
};
|
||||
quote! {
|
||||
// todo
|
||||
}
|
||||
__private::instruction::#variant_arm => {
|
||||
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
@ -72,7 +85,12 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
.iter()
|
||||
.map(|rpc| {
|
||||
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
let variant_arm = generate_ix_variant(program, rpc);
|
||||
let variant_arm = generate_ix_variant(
|
||||
program,
|
||||
rpc.raw_method.sig.ident.to_string(),
|
||||
&rpc.args,
|
||||
false,
|
||||
);
|
||||
let rpc_name = &rpc.raw_method.sig.ident;
|
||||
quote! {
|
||||
__private::instruction::#variant_arm => {
|
||||
|
@ -85,9 +103,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
quote! {
|
||||
match ix {
|
||||
#ctor_state_dispatch_arm
|
||||
|
||||
#(#state_dispatch_arms),*
|
||||
|
||||
#(#dispatch_arms),*
|
||||
}
|
||||
}
|
||||
|
@ -105,14 +121,27 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
let ctor_untyped_args = generate_ctor_args(state);
|
||||
let name = &state.strct.ident;
|
||||
let mod_name = &program.name;
|
||||
let anchor_ident = &state.ctor_anchor;
|
||||
quote! {
|
||||
#[inline(never)]
|
||||
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
|
||||
let mut accounts: &[AccountInfo] = accounts;
|
||||
let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut accounts)?;
|
||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||
|
||||
let instance = #mod_name::#name::new(#(#ctor_untyped_args),*)?;
|
||||
// Deserialize accounts.
|
||||
let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
|
||||
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
|
||||
|
||||
// Invoke the ctor.
|
||||
let instance = #mod_name::#name::new(
|
||||
anchor_lang::Context::new(
|
||||
program_id,
|
||||
&mut ctor_user_def_accounts,
|
||||
remaining_accounts,
|
||||
),
|
||||
#(#ctor_untyped_args),*
|
||||
)?;
|
||||
|
||||
// Create the solana account for the ctor data.
|
||||
let from = ctor_accounts.from.key;
|
||||
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
|
||||
let seed = anchor_lang::ProgramState::<#name>::seed();
|
||||
|
@ -122,8 +151,6 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
|
||||
let lamports = ctor_accounts.rent.minimum_balance(space);
|
||||
let seeds = &[&[nonce][..]];
|
||||
|
||||
// Create the new program owned account (from within the program).
|
||||
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
|
||||
from,
|
||||
&to,
|
||||
|
@ -145,6 +172,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
)?;
|
||||
|
||||
// Serialize the state and save it to storage.
|
||||
ctor_user_def_accounts.exit(program_id)?;
|
||||
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
|
@ -155,6 +183,71 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
}
|
||||
}
|
||||
};
|
||||
let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
|
||||
None => vec![],
|
||||
Some(state) => state
|
||||
.methods
|
||||
.iter()
|
||||
.map(|rpc| {
|
||||
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
|
||||
let rpc_arg_names: Vec<&syn::Ident> =
|
||||
rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
let private_rpc_name: proc_macro2::TokenStream = {
|
||||
let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
|
||||
n.parse().unwrap()
|
||||
};
|
||||
let rpc_name = &rpc.raw_method.sig.ident;
|
||||
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
|
||||
let anchor_ident = &rpc.anchor_ident;
|
||||
quote! {
|
||||
#[inline(never)]
|
||||
pub fn #private_rpc_name(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
#(#rpc_params),*
|
||||
) -> ProgramResult {
|
||||
|
||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||
if remaining_accounts.len() == 0 {
|
||||
return Err(ProgramError::Custom(1)); // todo
|
||||
}
|
||||
|
||||
// Deserialize the program state account.
|
||||
let state_account = &remaining_accounts[0];
|
||||
let mut state: #state_ty = {
|
||||
let data = state_account.try_borrow_data()?;
|
||||
let mut sliced: &[u8] = &data;
|
||||
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
|
||||
};
|
||||
|
||||
remaining_accounts = &remaining_accounts[1..];
|
||||
|
||||
// Deserialize the program's execution context.
|
||||
let mut accounts = #anchor_ident::try_accounts(
|
||||
program_id,
|
||||
&mut remaining_accounts,
|
||||
)?;
|
||||
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
||||
|
||||
// Execute user defined function.
|
||||
state.#rpc_name(
|
||||
ctx,
|
||||
#(#rpc_arg_names),*
|
||||
)?;
|
||||
|
||||
// Serialize the state and save it to storage.
|
||||
accounts.exit(program_id)?;
|
||||
let mut data = state_account.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
state.try_serialize(&mut cursor)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
|
||||
.rpcs
|
||||
.iter()
|
||||
|
@ -185,7 +278,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
|
||||
quote! {
|
||||
#non_inlined_ctor
|
||||
|
||||
#(#non_inlined_state_handlers)*
|
||||
#(#non_inlined_handlers)*
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +306,7 @@ pub fn generate_ctor_typed_variant_with_comma(program: &Program) -> proc_macro2:
|
|||
let ctor_args = generate_ctor_typed_args(state);
|
||||
if ctor_args.len() == 0 {
|
||||
quote! {
|
||||
__Ctor
|
||||
__Ctor,
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
|
@ -232,9 +325,16 @@ fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
|
|||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(pat_ty) => pat_ty.clone(),
|
||||
_ => panic!(""),
|
||||
.filter_map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(pat_ty) => {
|
||||
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
|
||||
arg_str.retain(|c| !c.is_whitespace());
|
||||
if arg_str.starts_with("Context<") {
|
||||
return None;
|
||||
}
|
||||
Some(pat_ty.clone())
|
||||
}
|
||||
_ => panic!("Invalid syntaxe,"),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -245,21 +345,38 @@ fn generate_ctor_args(state: &State) -> Vec<Box<syn::Pat>> {
|
|||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(pat_ty) => pat_ty.pat.clone(),
|
||||
.filter_map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(pat_ty) => {
|
||||
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
|
||||
arg_str.retain(|c| !c.is_whitespace());
|
||||
if arg_str.starts_with("Context<") {
|
||||
return None;
|
||||
}
|
||||
Some(pat_ty.pat.clone())
|
||||
}
|
||||
_ => panic!(""),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
|
||||
pub fn generate_ix_variant(
|
||||
program: &Program,
|
||||
name: String,
|
||||
args: &[RpcArg],
|
||||
underscore: bool,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let enum_name = instruction_enum_name(program);
|
||||
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
|
||||
let rpc_name_camel = proc_macro2::Ident::new(
|
||||
&rpc.raw_method.sig.ident.to_string().to_camel_case(),
|
||||
rpc.raw_method.sig.ident.span(),
|
||||
);
|
||||
if rpc.args.len() == 0 {
|
||||
let rpc_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
|
||||
let rpc_name_camel: proc_macro2::TokenStream = {
|
||||
let n = name.to_camel_case();
|
||||
if underscore {
|
||||
format!("__{}", n).parse().unwrap()
|
||||
} else {
|
||||
n.parse().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
if args.len() == 0 {
|
||||
quote! {
|
||||
#enum_name::#rpc_name_camel
|
||||
}
|
||||
|
@ -282,6 +399,36 @@ pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
|
|||
pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
|
||||
let enum_name = instruction_enum_name(program);
|
||||
let ctor_variant = generate_ctor_typed_variant_with_comma(program);
|
||||
let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
|
||||
None => vec![],
|
||||
Some(state) => state
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method| {
|
||||
let rpc_name_camel: proc_macro2::TokenStream = {
|
||||
let name = format!(
|
||||
"__{}",
|
||||
&method.raw_method.sig.ident.to_string().to_camel_case(),
|
||||
);
|
||||
name.parse().unwrap()
|
||||
};
|
||||
let raw_args: Vec<&syn::PatType> =
|
||||
method.args.iter().map(|arg| &arg.raw_arg).collect();
|
||||
// If no args, output a "unit" variant instead of a struct variant.
|
||||
if method.args.len() == 0 {
|
||||
quote! {
|
||||
#rpc_name_camel,
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#rpc_name_camel {
|
||||
#(#raw_args),*
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let variants: Vec<proc_macro2::TokenStream> = program
|
||||
.rpcs
|
||||
.iter()
|
||||
|
@ -312,6 +459,7 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
|
|||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub enum #enum_name {
|
||||
#ctor_variant
|
||||
#(#state_method_variants)*
|
||||
#(#variants),*
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +480,12 @@ fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
|
|||
.map(|rpc| {
|
||||
let accounts_ident = &rpc.anchor_ident;
|
||||
let cpi_method = {
|
||||
let ix_variant = generate_ix_variant(program, rpc);
|
||||
let ix_variant = generate_ix_variant(
|
||||
program,
|
||||
rpc.raw_method.sig.ident.to_string(),
|
||||
&rpc.args,
|
||||
false,
|
||||
);
|
||||
let method_name = &rpc.ident;
|
||||
let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
|
||||
quote! {
|
||||
|
|
|
@ -24,13 +24,7 @@ pub struct IdlState {
|
|||
pub methods: Vec<IdlStateMethod>,
|
||||
}
|
||||
|
||||
// IdlStateMethods are similar to instructions, except they only allow
|
||||
// for a single account, the state account.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct IdlStateMethod {
|
||||
pub name: String,
|
||||
pub args: Vec<IdlField>,
|
||||
}
|
||||
pub type IdlStateMethod = IdlInstruction;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct IdlInstruction {
|
||||
|
|
|
@ -27,8 +27,17 @@ pub struct State {
|
|||
pub name: String,
|
||||
pub strct: syn::ItemStruct,
|
||||
pub impl_block: syn::ItemImpl,
|
||||
pub methods: Vec<Rpc>,
|
||||
pub methods: Vec<StateRpc>,
|
||||
pub ctor: syn::ImplItemMethod,
|
||||
pub ctor_anchor: syn::Ident, // TODO: consolidate this with ctor above.
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StateRpc {
|
||||
pub raw_method: syn::ImplItemMethod,
|
||||
pub ident: syn::Ident,
|
||||
pub args: Vec<RpcArg>,
|
||||
pub anchor_ident: syn::Ident,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::idl::*;
|
||||
use crate::parser::{self, accounts, error, program};
|
||||
use crate::{AccountsStruct, Rpc};
|
||||
use crate::{AccountsStruct, StateRpc};
|
||||
use anyhow::Result;
|
||||
use heck::MixedCase;
|
||||
use quote::ToTokens;
|
||||
|
@ -22,12 +22,23 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
|||
|
||||
let p = program::parse(parse_program_mod(&f));
|
||||
|
||||
let accs = parse_accounts(&f);
|
||||
let acc_names = {
|
||||
let mut acc_names = HashSet::new();
|
||||
for accs_strct in accs.values() {
|
||||
for a in accs_strct.account_tys(&accs)? {
|
||||
acc_names.insert(a);
|
||||
}
|
||||
}
|
||||
acc_names
|
||||
};
|
||||
|
||||
let state = p.state.map(|state| {
|
||||
let mut methods = state
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method: &Rpc| {
|
||||
let name = method.ident.to_string();
|
||||
.map(|method: &StateRpc| {
|
||||
let name = method.ident.to_string().to_mixed_case();
|
||||
let args = method
|
||||
.args
|
||||
.iter()
|
||||
|
@ -41,7 +52,13 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
|||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
IdlStateMethod { name, args }
|
||||
let accounts_strct = accs.get(&method.anchor_ident.to_string()).unwrap();
|
||||
let accounts = accounts_strct.idl_accounts(&accs);
|
||||
IdlStateMethod {
|
||||
name,
|
||||
args,
|
||||
accounts,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let ctor = {
|
||||
|
@ -51,6 +68,18 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
|||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(pat_ty) => {
|
||||
// TODO: this filtering should be donein the parser.
|
||||
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
|
||||
arg_str.retain(|c| !c.is_whitespace());
|
||||
if arg_str.starts_with("Context<") {
|
||||
return None;
|
||||
}
|
||||
Some(arg)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(|arg: &syn::FnArg| match arg {
|
||||
syn::FnArg::Typed(arg_typed) => {
|
||||
let mut tts = proc_macro2::TokenStream::new();
|
||||
|
@ -64,7 +93,13 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
|||
_ => panic!("Invalid syntax"),
|
||||
})
|
||||
.collect();
|
||||
IdlStateMethod { name, args }
|
||||
let accounts_strct = accs.get(&state.ctor_anchor.to_string()).unwrap();
|
||||
let accounts = accounts_strct.idl_accounts(&accs);
|
||||
IdlStateMethod {
|
||||
name,
|
||||
args,
|
||||
accounts,
|
||||
}
|
||||
};
|
||||
|
||||
methods.insert(0, ctor);
|
||||
|
@ -106,20 +141,6 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
|||
.collect::<Vec<IdlErrorCode>>()
|
||||
});
|
||||
|
||||
let accs = parse_accounts(&f);
|
||||
|
||||
let acc_names = {
|
||||
let mut acc_names = HashSet::new();
|
||||
|
||||
for accs_strct in accs.values() {
|
||||
for a in accs_strct.account_tys(&accs)? {
|
||||
acc_names.insert(a);
|
||||
}
|
||||
}
|
||||
|
||||
acc_names
|
||||
};
|
||||
|
||||
let instructions = p
|
||||
.rpcs
|
||||
.iter()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::parser;
|
||||
use crate::{Program, Rpc, RpcArg, State};
|
||||
use crate::{Program, Rpc, RpcArg, State, StateRpc};
|
||||
|
||||
pub fn parse(program_mod: syn::ItemMod) -> Program {
|
||||
let mod_ident = &program_mod.ident;
|
||||
|
@ -52,13 +52,19 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
|
|||
strct.attrs = vec![];
|
||||
|
||||
let impl_block = impl_block.expect("Must exist if struct exists").clone();
|
||||
let ctor = impl_block
|
||||
let (ctor, ctor_anchor) = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item: &syn::ImplItem| match item {
|
||||
syn::ImplItem::Method(m) => {
|
||||
if m.sig.ident.to_string() == "new" {
|
||||
Some(m)
|
||||
let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
|
||||
match ctx_arg {
|
||||
syn::FnArg::Receiver(_) => panic!("invalid syntax"),
|
||||
syn::FnArg::Typed(arg) => {
|
||||
Some((m.clone(), extract_ident(&arg).clone()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -68,12 +74,58 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
|
|||
.next()
|
||||
.expect("Must exist if struct exists")
|
||||
.clone();
|
||||
|
||||
let methods: Vec<StateRpc> = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item: &syn::ImplItem| match item {
|
||||
syn::ImplItem::Method(m) => match m.sig.inputs.first() {
|
||||
None => None,
|
||||
Some(arg) => match arg {
|
||||
syn::FnArg::Typed(_) => None,
|
||||
syn::FnArg::Receiver(_) => {
|
||||
let mut args = m
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
syn::FnArg::Receiver(_) => None,
|
||||
syn::FnArg::Typed(arg) => Some(arg),
|
||||
})
|
||||
.map(|raw_arg| {
|
||||
let ident = match &*raw_arg.pat {
|
||||
syn::Pat::Ident(ident) => &ident.ident,
|
||||
_ => panic!("invalid syntax"),
|
||||
};
|
||||
RpcArg {
|
||||
name: ident.clone(),
|
||||
raw_arg: raw_arg.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<RpcArg>>();
|
||||
// Remove the Anchor accounts argument
|
||||
let anchor = args.remove(0);
|
||||
let anchor_ident = extract_ident(&anchor.raw_arg).clone();
|
||||
|
||||
Some(StateRpc {
|
||||
raw_method: m.clone(),
|
||||
ident: m.sig.ident.clone(),
|
||||
args,
|
||||
anchor_ident,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
State {
|
||||
name: strct.ident.to_string(),
|
||||
strct: strct.clone(),
|
||||
impl_block,
|
||||
ctor,
|
||||
methods: vec![], // todo
|
||||
ctor_anchor,
|
||||
methods,
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@project-serum/anchor",
|
||||
"version": "0.0.0-alpha.6",
|
||||
"version": "0.0.0-alpha.7",
|
||||
"description": "Anchor client",
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
|
|
|
@ -21,10 +21,7 @@ export type IdlState = {
|
|||
methods: IdlStateMethod[];
|
||||
};
|
||||
|
||||
export type IdlStateMethod = {
|
||||
name: string;
|
||||
args: IdlField[];
|
||||
};
|
||||
export type IdlStateMethod = IdlInstruction;
|
||||
|
||||
export type IdlAccountItem = IdlAccount | IdlAccounts;
|
||||
|
||||
|
|
131
ts/src/rpc.ts
131
ts/src/rpc.ts
|
@ -131,7 +131,7 @@ export class RpcFactory {
|
|||
const rpcs: Rpcs = {};
|
||||
const ixFns: Ixs = {};
|
||||
const txFns: Txs = {};
|
||||
const state = RpcFactory.buildState(idl, coder, programId);
|
||||
const state = RpcFactory.buildState(idl, coder, programId, idlErrors);
|
||||
|
||||
idl.instructions.forEach((idlIx) => {
|
||||
// Function to create a raw `TransactionInstruction`.
|
||||
|
@ -157,7 +157,8 @@ export class RpcFactory {
|
|||
private static buildState(
|
||||
idl: Idl,
|
||||
coder: Coder,
|
||||
programId: PublicKey
|
||||
programId: PublicKey,
|
||||
idlErrors: Map<number, string>
|
||||
): State | undefined {
|
||||
if (idl.state === undefined) {
|
||||
return undefined;
|
||||
|
@ -172,52 +173,92 @@ export class RpcFactory {
|
|||
|
||||
const rpc: Rpcs = {};
|
||||
idl.state.methods.forEach((m: IdlStateMethod) => {
|
||||
if (m.name !== "new") {
|
||||
throw new Error("State struct mutatation not yet implemented.");
|
||||
}
|
||||
// Ctor `new` method.
|
||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
||||
const tx = new Transaction();
|
||||
const [programSigner, _nonce] = await PublicKey.findProgramAddress(
|
||||
[],
|
||||
programId
|
||||
);
|
||||
const ix = new TransactionInstruction({
|
||||
keys: [
|
||||
{
|
||||
pubkey: getProvider().wallet.publicKey,
|
||||
isWritable: false,
|
||||
isSigner: true,
|
||||
},
|
||||
if (m.name === "new") {
|
||||
// Ctor `new` method.
|
||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
||||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
|
||||
const tx = new Transaction();
|
||||
const [programSigner, _nonce] = await PublicKey.findProgramAddress(
|
||||
[],
|
||||
programId
|
||||
);
|
||||
const ix = new TransactionInstruction({
|
||||
keys: [
|
||||
{
|
||||
pubkey: getProvider().wallet.publicKey,
|
||||
isWritable: false,
|
||||
isSigner: true,
|
||||
},
|
||||
{ pubkey: await address(), isWritable: true, isSigner: false },
|
||||
{ pubkey: programSigner, isWritable: false, isSigner: false },
|
||||
{
|
||||
pubkey: SystemProgram.programId,
|
||||
isWritable: false,
|
||||
isSigner: false,
|
||||
},
|
||||
|
||||
{ pubkey: programId, isWritable: false, isSigner: false },
|
||||
{
|
||||
pubkey: SYSVAR_RENT_PUBKEY,
|
||||
isWritable: false,
|
||||
isSigner: false,
|
||||
},
|
||||
].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts)),
|
||||
programId,
|
||||
data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
|
||||
});
|
||||
|
||||
tx.add(ix);
|
||||
|
||||
const provider = getProvider();
|
||||
if (provider === null) {
|
||||
throw new Error("Provider not found");
|
||||
}
|
||||
try {
|
||||
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
||||
return txSig;
|
||||
} catch (err) {
|
||||
let translatedErr = translateError(idlErrors, err);
|
||||
if (translatedErr === null) {
|
||||
throw err;
|
||||
}
|
||||
throw translatedErr;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
||||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
|
||||
validateAccounts(m.accounts, ctx.accounts);
|
||||
const tx = new Transaction();
|
||||
|
||||
const keys = [
|
||||
{ pubkey: await address(), isWritable: true, isSigner: false },
|
||||
{ pubkey: programSigner, isWritable: false, isSigner: false },
|
||||
{
|
||||
pubkey: SystemProgram.programId,
|
||||
isWritable: false,
|
||||
isSigner: false,
|
||||
},
|
||||
].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts));
|
||||
|
||||
{ pubkey: programId, isWritable: false, isSigner: false },
|
||||
{ pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
|
||||
],
|
||||
programId,
|
||||
data: coder.instruction.encode(toInstruction(m, ...args)),
|
||||
});
|
||||
tx.add(
|
||||
new TransactionInstruction({
|
||||
keys,
|
||||
programId,
|
||||
data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
|
||||
})
|
||||
);
|
||||
|
||||
tx.add(ix);
|
||||
|
||||
const provider = getProvider();
|
||||
if (provider === null) {
|
||||
throw new Error("Provider not found");
|
||||
}
|
||||
try {
|
||||
const txSig = await provider.send(tx);
|
||||
return txSig;
|
||||
} catch (err) {
|
||||
// TODO: translate error.
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
const provider = getProvider();
|
||||
if (provider === null) {
|
||||
throw new Error("Provider not found");
|
||||
}
|
||||
try {
|
||||
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
||||
return txSig;
|
||||
} catch (err) {
|
||||
let translatedErr = translateError(idlErrors, err);
|
||||
if (translatedErr === null) {
|
||||
throw err;
|
||||
}
|
||||
throw translatedErr;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Fetches the state object from the blockchain.
|
||||
|
|
Loading…
Reference in New Issue