lang: Add state size override (#121)
This commit is contained in:
parent
0a4eb05c68
commit
b7cafcda0e
|
@ -14,6 +14,7 @@ incremented for features.
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* cli: Specify test files to run ([#118](https://github.com/project-serum/anchor/pull/118)).
|
* cli: Specify test files to run ([#118](https://github.com/project-serum/anchor/pull/118)).
|
||||||
|
* lang: Allow overriding the `#[state]` account's size ([#121](https://github.com/project-serum/anchor/pull/121)).
|
||||||
|
|
||||||
## [0.3.0] - 2021-03-12
|
## [0.3.0] - 2021-03-12
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,20 @@ use anchor_lang::prelude::*;
|
||||||
#[program]
|
#[program]
|
||||||
pub mod misc {
|
pub mod misc {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub const SIZE: u64 = 99;
|
||||||
|
|
||||||
|
#[state(SIZE)]
|
||||||
|
pub struct MyState {
|
||||||
|
pub v: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyState {
|
||||||
|
pub fn new(_ctx: Context<Ctor>) -> Result<Self, ProgramError> {
|
||||||
|
Ok(Self { v: vec![] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> ProgramResult {
|
pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> ProgramResult {
|
||||||
ctx.accounts.data.udata = udata;
|
ctx.accounts.data.udata = udata;
|
||||||
ctx.accounts.data.idata = idata;
|
ctx.accounts.data.idata = idata;
|
||||||
|
@ -15,6 +29,9 @@ pub mod misc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Ctor {}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct Initialize<'info> {
|
pub struct Initialize<'info> {
|
||||||
#[account(init)]
|
#[account(init)]
|
||||||
|
|
|
@ -5,10 +5,19 @@ const assert = require("assert");
|
||||||
describe("misc", () => {
|
describe("misc", () => {
|
||||||
// Configure the client to use the local cluster.
|
// Configure the client to use the local cluster.
|
||||||
anchor.setProvider(anchor.Provider.env());
|
anchor.setProvider(anchor.Provider.env());
|
||||||
|
const program = anchor.workspace.Misc;
|
||||||
|
|
||||||
|
it("Can allocate extra space for a state constructor", async () => {
|
||||||
|
const tx = await program.state.rpc.new();
|
||||||
|
const addr = await program.state.address();
|
||||||
|
const state = await program.state();
|
||||||
|
const accountInfo = await program.provider.connection.getAccountInfo(addr);
|
||||||
|
assert.ok(state.v.equals(Buffer.from([])));
|
||||||
|
assert.ok(accountInfo.data.length === 99);
|
||||||
|
});
|
||||||
|
|
||||||
it("Can use u128 and i128", async () => {
|
it("Can use u128 and i128", async () => {
|
||||||
const data = new anchor.web3.Account();
|
const data = new anchor.web3.Account();
|
||||||
const program = anchor.workspace.Misc;
|
|
||||||
const tx = await program.rpc.initialize(
|
const tx = await program.rpc.initialize(
|
||||||
new anchor.BN(1234),
|
new anchor.BN(1234),
|
||||||
new anchor.BN(22),
|
new anchor.BN(22),
|
||||||
|
|
|
@ -19,23 +19,15 @@ use syn::parse_macro_input;
|
||||||
/// and the account deserialization will exit with an error.
|
/// and the account deserialization will exit with an error.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn account(
|
pub fn account(
|
||||||
args: proc_macro::TokenStream,
|
_args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
let namespace = args.to_string().replace("\"", "");
|
|
||||||
|
|
||||||
let account_strct = parse_macro_input!(input as syn::ItemStruct);
|
let account_strct = parse_macro_input!(input as syn::ItemStruct);
|
||||||
let account_name = &account_strct.ident;
|
let account_name = &account_strct.ident;
|
||||||
|
|
||||||
let discriminator: proc_macro2::TokenStream = {
|
let discriminator: proc_macro2::TokenStream = {
|
||||||
// Namespace the discriminator to prevent collisions.
|
// Namespace the discriminator to prevent collisions.
|
||||||
let discriminator_preimage = {
|
let discriminator_preimage = format!("account:{}", account_name.to_string());
|
||||||
if namespace.is_empty() {
|
|
||||||
format!("account:{}", account_name.to_string())
|
|
||||||
} else {
|
|
||||||
format!("{}:{}", namespace, account_name.to_string())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut discriminator = [0u8; 8];
|
let mut discriminator = [0u8; 8];
|
||||||
discriminator.copy_from_slice(
|
discriminator.copy_from_slice(
|
||||||
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
||||||
|
@ -57,7 +49,6 @@ pub fn account(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl anchor_lang::AccountDeserialize for #account_name {
|
impl anchor_lang::AccountDeserialize for #account_name {
|
||||||
|
|
||||||
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
||||||
if buf.len() < #discriminator.len() {
|
if buf.len() < #discriminator.len() {
|
||||||
return Err(ProgramError::AccountDataTooSmall);
|
return Err(ProgramError::AccountDataTooSmall);
|
||||||
|
|
|
@ -5,15 +5,52 @@ use syn::parse_macro_input;
|
||||||
|
|
||||||
/// The `#[state]` attribute defines the program's state struct, i.e., the
|
/// The `#[state]` attribute defines the program's state struct, i.e., the
|
||||||
/// program's global account singleton giving the program the illusion of state.
|
/// program's global account singleton giving the program the illusion of state.
|
||||||
|
///
|
||||||
|
/// To allocate space into the account on initialization, pass in the account
|
||||||
|
/// size into the macro, e.g., `#[state(SIZE)]`. Otherwise, the size of the
|
||||||
|
/// account returned by the struct's `new` constructor will determine the
|
||||||
|
/// account size. When determining a size, make sure to reserve enough space
|
||||||
|
/// for the 8 byte account discriminator prepended to the account. That is,
|
||||||
|
/// always use 8 extra bytes.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn state(
|
pub fn state(
|
||||||
_args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
let item_struct = parse_macro_input!(input as syn::ItemStruct);
|
let item_struct = parse_macro_input!(input as syn::ItemStruct);
|
||||||
|
let struct_ident = &item_struct.ident;
|
||||||
|
|
||||||
|
let size_override = {
|
||||||
|
if args.is_empty() {
|
||||||
|
// No size override given. The account size is whatever is given
|
||||||
|
// as the initialized value. Use the default implementation.
|
||||||
|
quote! {
|
||||||
|
impl anchor_lang::AccountSize for #struct_ident {
|
||||||
|
fn size(&self) -> Result<u64, ProgramError> {
|
||||||
|
Ok(8 + self
|
||||||
|
.try_to_vec()
|
||||||
|
.map_err(|_| ProgramError::Custom(1))?
|
||||||
|
.len() as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let size = proc_macro2::TokenStream::from(args);
|
||||||
|
// Size override given to the macro. Use it.
|
||||||
|
quote! {
|
||||||
|
impl anchor_lang::AccountSize for #struct_ident {
|
||||||
|
fn size(&self) -> Result<u64, ProgramError> {
|
||||||
|
Ok(#size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
proc_macro::TokenStream::from(quote! {
|
proc_macro::TokenStream::from(quote! {
|
||||||
#[account]
|
#[account]
|
||||||
#item_struct
|
#item_struct
|
||||||
|
|
||||||
|
#size_override
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,6 +169,12 @@ pub trait InstructionData: AnchorSerialize {
|
||||||
fn data(&self) -> Vec<u8>;
|
fn data(&self) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates the size of an account, which may be larger than the deserialized
|
||||||
|
/// data in it. This trait is currently only used for `#[state]` accounts.
|
||||||
|
pub trait AccountSize: AnchorSerialize {
|
||||||
|
fn size(&self) -> Result<u64, ProgramError>;
|
||||||
|
}
|
||||||
|
|
||||||
/// The prelude contains all commonly used components of the crate.
|
/// The prelude contains all commonly used components of the crate.
|
||||||
/// All programs should include it via `anchor_lang::prelude::*;`.
|
/// All programs should include it via `anchor_lang::prelude::*;`.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|
|
@ -420,9 +420,8 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
||||||
let seed = anchor_lang::ProgramState::<#name>::seed();
|
let seed = anchor_lang::ProgramState::<#name>::seed();
|
||||||
let owner = ctor_accounts.program.key;
|
let owner = ctor_accounts.program.key;
|
||||||
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
|
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
|
||||||
// Add 8 for the account discriminator.
|
let space = anchor_lang::AccountSize::size(&instance)?;
|
||||||
let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
|
let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
|
||||||
let lamports = ctor_accounts.rent.minimum_balance(space);
|
|
||||||
let seeds = &[&[nonce][..]];
|
let seeds = &[&[nonce][..]];
|
||||||
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
|
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
|
||||||
from,
|
from,
|
||||||
|
@ -430,7 +429,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
||||||
&base,
|
&base,
|
||||||
seed,
|
seed,
|
||||||
lamports,
|
lamports,
|
||||||
space as u64,
|
space,
|
||||||
owner,
|
owner,
|
||||||
);
|
);
|
||||||
anchor_lang::solana_program::program::invoke_signed(
|
anchor_lang::solana_program::program::invoke_signed(
|
||||||
|
|
Loading…
Reference in New Issue