Skip to content

Commit

Permalink
lang: adjust realloc implementation to safeguard max increase and i…
Browse files Browse the repository at this point in the history
…dempotency (#1986)
  • Loading branch information
callensm authored Jul 5, 2022
1 parent 9b61bbc commit c47fb28
Show file tree
Hide file tree
Showing 38 changed files with 786 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ jobs:
path: tests/escrow
- cmd: cd tests/pyth && anchor test --skip-lint && npx tsc --noEmit
path: tests/pyth
- cmd: cd tests/realloc && anchor test --skip-lint && npx tsc --noEmit
path: tests/realloc
- cmd: cd tests/system-accounts && anchor test --skip-lint
path: tests/system-accounts
- cmd: cd tests/misc && anchor test --skip-lint && npx tsc --noEmit
Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ The minor version will be incremented upon a breaking change and the patch versi

### Features

* lang: Add `realloc`, `realloc::payer`, and `realloc::zero` as a new constraint group for program accounts ([#1986](https://github.com/coral-xyz/anchor/pull/1986)).
* lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/coral-xyz/anchor/pull/1544)).
* cli: Add `--skip-build` to `anchor publish` ([#1786](https://github.
com/project-serum/anchor/pull/1841)).
* cli: Add `--skip-build` to `anchor publish` ([#1786](https://github.com/coral-xyz/anchor/pull/1841)).
* cli: Add `--program-keypair` to `anchor deploy` ([#1786](https://github.com/coral-xyz/anchor/pull/1786)).
* cli: Add compilation optimizations to cli template ([#1807](https://github.com/coral-xyz/anchor/pull/1807)).
* cli: `build` now adds docs to idl. This can be turned off with `--no-docs` ([#1561](https://github.com/coral-xyz/anchor/pull/1561)).
Expand All @@ -41,6 +41,7 @@ com/project-serum/anchor/pull/1841)).
* ts: Change `BROWSER` env variable to `ANCHOR_BROWSER` ([#1233](https://github.com/coral-xyz/anchor/pull/1233)).
* ts: Add transaction signature to `EventCallback` parameters ([#1851](https://github.com/coral-xyz/anchor/pull/1851)).
* ts: Change `EventParser#parseLogs` implementation to be a generator instead of callback function ([#2018](https://github.com/coral-xyz/anchor/pull/2018)).
* lang: Adds a new `&mut reallocs: BTreeSet<Pubkey>` argument to `Accounts::try_accounts` ([#1986](https://github.com/coral-xyz/anchor/pull/1986)).

## [0.24.2] - 2022-04-13

Expand Down
43 changes: 43 additions & 0 deletions lang/derive/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use syn::parse_macro_input;
///
/// - [Normal Constraints](#normal-constraints)
/// - [SPL Constraints](#spl-constraints)
///
/// # Normal Constraints
/// <table>
/// <thead>
Expand Down Expand Up @@ -418,6 +419,48 @@ use syn::parse_macro_input;
/// </code></pre>
/// </td>
/// </tr>
/// <tr>
/// <td>
/// <code>#[account(realloc = &lt;space&gt;, realloc::payer = &lt;target&gt;, realloc::zero = &lt;bool&gt;)]</code>
/// </td>
/// <td>
/// Used to <a href="https://docs.rs/solana-program/latest/solana_program/account_info/struct.AccountInfo.html#method.realloc" target = "_blank" rel = "noopener noreferrer">realloc</a>
/// program account space at the beginning of an instruction.
/// <br><br>
/// The account must be marked as <code>mut</code> and applied to either <code>Account</code> or <code>AccountLoader</code> types.
/// <br><br>
/// If the change in account data length is additive, lamports will be transferred from the <code>realloc::payer</code> into the
/// program account in order to maintain rent exemption. Likewise, if the change is subtractive, lamports will be transferred from
/// the program account back into the <code>realloc::payer</code>.
/// <br><br>
/// The <code>realloc::zero</code> constraint is required in order to determine whether the new memory should be zero initialized after
/// reallocation. Please read the documentation on the <code>AccountInfo::realloc</code> function linked above to understand the
/// caveats regarding compute units when providing <code>true</code or <code>false</code> to this flag.
/// <br><br>
/// The manual use of `AccountInfo::realloc` is discouraged in favor of the `realloc` constraint group due to the lack of native runtime checks
/// to prevent reallocation over the `MAX_PERMITTED_DATA_INCREASE` limit (which can unintentionally cause account data overwrite other accounts).
/// The constraint group also ensure account reallocation idempotency but checking and restricting duplicate account reallocation within a single ix.
/// <br><br>
/// Example:
/// <pre>
/// #[derive(Accounts)]
/// pub struct Example {
/// #[account(mut)]
/// pub payer: Signer<'info>,
/// #[account(
/// mut,
/// seeds = [b"example"],
/// bump,
/// realloc = 8 + std::mem::size_of::<MyType>() + 100,
/// realloc::payer = payer,
/// realloc::zero = false,
/// )]
/// pub acc: Account<'info, MyType>,
/// pub system_program: Program<'info, System>,
/// }
/// </pre>
/// </td>
/// </tr>
/// </tbody>
/// </table>
///
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use solana_program::system_program;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::ops::{Deref, DerefMut};

Expand Down Expand Up @@ -321,6 +321,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/account_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas}
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};

impl<'info> Accounts<'info> for AccountInfo<'info> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/account_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::cell::{Ref, RefMut};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::io::Write;
use std::marker::PhantomData;
Expand Down Expand Up @@ -221,6 +221,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
5 changes: 3 additions & 2 deletions lang/src/accounts/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{Accounts, AccountsClose, AccountsExit, Result, ToAccountInfos, ToAcc
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Deref;

impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
Expand All @@ -26,8 +26,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
T::try_accounts(program_id, accounts, ix_data, bumps).map(Box::new)
T::try_accounts(program_id, accounts, ix_data, bumps, reallocs).map(Box::new)
}
}

Expand Down
1 change: 1 addition & 0 deletions lang/src/accounts/cpi_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/cpi_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, DerefMut};

/// Boxed container for the program state singleton, used when the state
Expand Down Expand Up @@ -72,6 +72,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::cell::{Ref, RefMut};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::io::Write;
use std::marker::PhantomData;
Expand Down Expand Up @@ -163,6 +163,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
Expand Down Expand Up @@ -147,6 +147,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/program_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, DerefMut};

/// Boxed container for a deserialized `account`. Use this to reference any
Expand Down Expand Up @@ -83,6 +83,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas}
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Deref;

/// Type validating that the account signed the transaction. No other ownership
Expand Down Expand Up @@ -61,6 +61,7 @@ impl<'info> Accounts<'info> for Signer<'info> {
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, DerefMut};

pub const PROGRAM_STATE_SEED: &str = "unversioned";
Expand Down Expand Up @@ -74,6 +74,7 @@ where
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/system_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use solana_program::system_program;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Deref;

/// Type validating that the account is owned by the system program
Expand Down Expand Up @@ -40,6 +40,7 @@ impl<'info> Accounts<'info> for SystemAccount<'info> {
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/sysvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas}
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::ops::{Deref, DerefMut};

Expand Down Expand Up @@ -71,6 +71,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
3 changes: 2 additions & 1 deletion lang/src/accounts/unchecked_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas}
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Deref;

/// Explicit wrapper for AccountInfo types to emphasize
Expand All @@ -26,6 +26,7 @@ impl<'info> Accounts<'info> for UncheckedAccount<'info> {
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
Expand Down
6 changes: 6 additions & 0 deletions lang/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ pub enum ErrorCode {
/// 3015 - The given public key does not match the required sysvar
#[msg("The given public key does not match the required sysvar")]
AccountSysvarMismatch,
/// 3016 - The account reallocation exceeds the MAX_PERMITTED_DATA_INCREASE limit
#[msg("The account reallocation exceeds the MAX_PERMITTED_DATA_INCREASE limit")]
AccountReallocExceedsLimit,
/// 3017 - The account was duplicated for more than one reallocation
#[msg("The account was duplicated for more than one reallocation")]
AccountDuplicateReallocs,

// State.
/// 4000 - The given state account does not have the correct address
Expand Down
3 changes: 2 additions & 1 deletion lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use bytemuck::{Pod, Zeroable};
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::io::Write;

mod account_meta;
Expand Down Expand Up @@ -82,6 +82,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self>;
}

Expand Down
14 changes: 10 additions & 4 deletions lang/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{Accounts, Result, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};

impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Vec<T> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
Expand All @@ -26,9 +26,11 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Vec<T> {
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
let mut vec: Vec<T> = Vec::new();
T::try_accounts(program_id, accounts, ix_data, bumps).map(|item| vec.push(item))?;
T::try_accounts(program_id, accounts, ix_data, bumps, reallocs)
.map(|item| vec.push(item))?;
Ok(vec)
}
}
Expand Down Expand Up @@ -78,9 +80,11 @@ mod tests {
Epoch::default(),
);
let mut bumps = std::collections::BTreeMap::new();
let mut reallocs = std::collections::BTreeSet::new();
let mut accounts = &[account1, account2][..];
let parsed_accounts =
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap();
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps, &mut reallocs)
.unwrap();

assert_eq!(accounts.len(), parsed_accounts.len());
}
Expand All @@ -90,7 +94,9 @@ mod tests {
fn test_accounts_trait_for_vec_empty() {
let program_id = Pubkey::default();
let mut bumps = std::collections::BTreeMap::new();
let mut reallocs = std::collections::BTreeSet::new();
let mut accounts = &[][..];
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap();
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps, &mut reallocs)
.unwrap();
}
}
Loading

1 comment on commit c47fb28

@vercel
Copy link

@vercel vercel bot commented on c47fb28 Jul 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

anchor-docs – ./

anchor-docs-200ms.vercel.app
anchor-docs-git-master-200ms.vercel.app
anchor-lang.com
www.anchor-lang.com

Please sign in to comment.