Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: adjust realloc implementation to safeguard max increase and idempotency #1986

Merged
merged 14 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
callensm marked this conversation as resolved.
Show resolved Hide resolved
/// </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