Skip to content

Commit

Permalink
Merge branch 'master' into armani/ixdecode
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante committed Jun 9, 2021
2 parents f8cbc5d + 381a3df commit 24dd034
Show file tree
Hide file tree
Showing 21 changed files with 254 additions and 39 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cache: cargo
env:
global:
- NODE_VERSION="14.7.0"
- SOLANA_CLI_VERSION="1.7.1"
git:
submodules: true

Expand All @@ -27,9 +28,9 @@ _examples: &examples
- npm install -g @project-serum/common
- npm install -g @solana/spl-token
- sudo apt-get install -y pkg-config build-essential libudev-dev
- sh -c "$(curl -sSfL https://release.solana.com/v1.6.9/install)"
- sh -c "$(curl -sSfL https://release.solana.com/v${SOLANA_CLI_VERSION}/install)"
- export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
- export NODE_PATH="/home/travis/.nvm/versions/node/v$NODE_VERSION/lib/node_modules/:$NODE_PATH"
- export NODE_PATH="/home/travis/.nvm/versions/node/v${NODE_VERSION}/lib/node_modules/:$NODE_PATH"
- yes | solana-keygen new
- cargo install --path $TRAVIS_BUILD_DIR/cli anchor-cli --locked

Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ incremented for features.
* cli: Add `--program-name` option for build command to build a single program at a time ([#362](https://github.com/project-serum/anchor/pull/362)).
* cli, client: Parse custom cluster urls from str ([#369](https://github.com/project-serum/anchor/pull/369)).
* cli, client, lang: Update solana toolchain to v1.7.1 ([#368](https://github.com/project-serum/anchor/pull/369)).
* ts: Instruction decoding ([#]()).
* lang: Add `#[account(close = <destination>)]` constraint for closing accounts and sending the rent exemption lamports to a specified destination account ([#371](https://github.com/project-serum/anchor/pull/371)).
* ts: Instruction decoding ([#372](https://github.com/project-serum/anchor/pull/372)).

### Fixes

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ shellexpand = "2.1.0"
serde_yaml = "0.8"
toml = "0.5.8"
serde = { version = "1.0.122", features = ["derive"] }
solana-sdk = "1.6.6"
solana-program = "1.6.6"
solana-client = "1.6.6"
solana-sdk = "1.7.1"
solana-program = "1.7.1"
solana-client = "1.7.1"
serum-common = { git = "https://github.com/project-serum/serum-dex", features = ["client"] }
dirs = "3.0"
heck = "0.3.1"
Expand Down
4 changes: 2 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ anchor-lang = { path = "../lang", version = "0.7.0" }
anyhow = "1.0.32"
regex = "1.4.5"
serde = { version = "1.0.122", features = ["derive"] }
solana-client = "1.6.6"
solana-sdk = "1.6.6"
solana-client = "1.7.1"
solana-sdk = "1.7.1"
thiserror = "1.0.20"
url = "2.2.2"
2 changes: 1 addition & 1 deletion docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ANCHOR_CLI=v$(shell awk -F ' = ' '$$1 ~ /version/ { gsub(/[\"]/, "", $$2); print
#
# Solana toolchain.
#
SOLANA_CLI=v1.6.9
SOLANA_CLI=v1.7.1
#
# Build version should match the Anchor cli version.
#
Expand Down
11 changes: 11 additions & 0 deletions examples/misc/programs/misc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ pub mod misc {
ctx.accounts.data.data = data;
Ok(())
}

pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
Ok(())
}
}

#[derive(Accounts)]
Expand Down Expand Up @@ -117,6 +121,13 @@ pub struct TestStateCpi<'info> {
misc2_program: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct TestClose<'info> {
#[account(mut, close = sol_dest)]
data: ProgramAccount<'info, Data>,
sol_dest: AccountInfo<'info>,
}

// `my_account` is the associated token account being created.
// `authority` must be a `mut` and `signer` since it will pay for the creation
// of the associated token account. `state` is used as an association, i.e., one
Expand Down
55 changes: 54 additions & 1 deletion examples/misc/tests/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,60 @@ describe("misc", () => {
});

it("Can use base58 strings to fetch an account", async () => {
const dataAccount = await program.account.dataI16.fetch(dataPubkey.toString());
const dataAccount = await program.account.dataI16.fetch(
dataPubkey.toString()
);
assert.ok(dataAccount.data === -2048);
});

it("Should fail to close an account when sending lamports to itself", async () => {
try {
await program.rpc.testClose({
accounts: {
data: data.publicKey,
solDest: data.publicKey,
},
});
assert.ok(false);
} catch (err) {
const errMsg = "A close constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 151);
}
});

it("Can close an account", async () => {
const openAccount = await program.provider.connection.getAccountInfo(
data.publicKey
);
assert.ok(openAccount !== null);

let beforeBalance = (
await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
)
).lamports;

await program.rpc.testClose({
accounts: {
data: data.publicKey,
solDest: program.provider.wallet.publicKey,
},
});

let afterBalance = (
await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
)
).lamports;

// Retrieved rent exemption sol.
assert.ok(afterBalance > beforeBalance);

const closedAccount = await program.provider.connection.getAccountInfo(
data.publicKey
);
assert.ok(closedAccount === null);
});
});
2 changes: 1 addition & 1 deletion lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ anchor-derive-accounts = { path = "./derive/accounts", version = "0.7.0" }
base64 = "0.13.0"
borsh = "0.8.2"
bytemuck = "1.4.0"
solana-program = "1.6.6"
solana-program = "1.7.1"
thiserror = "1.0.20"
1 change: 1 addition & 0 deletions lang/derive/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use syn::parse_macro_input;
/// | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
/// | `#[account(mut)]` | On `AccountInfo`, `ProgramAccount` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. When using `init`, a `rent` `Sysvar` must be present in the `Accounts` struct. |
/// | `#[account(close = <target>)]` | On `ProgramAccount` and `Loader` structs. | Marks the account as being closed at the end of the instruction's execution, sending the rent exemption lamports to the specified <target>. |
/// | `#[account(belongs_to = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
/// | `#[account(has_one = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Semantically different, but otherwise the same as `belongs_to`. |
/// | `#[account(seeds = [<seeds>])]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. |
Expand Down
24 changes: 24 additions & 0 deletions lang/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::error::ErrorCode;
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use std::io::Write;

pub fn close<'info>(
info: AccountInfo<'info>,
sol_destination: AccountInfo<'info>,
) -> ProgramResult {
// Transfer tokens from the account to the sol_destination.
let dest_starting_lamports = sol_destination.lamports();
**sol_destination.lamports.borrow_mut() =
dest_starting_lamports.checked_add(info.lamports()).unwrap();
**info.lamports.borrow_mut() = 0;

// Mark the account discriminator as closed.
let mut data = info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
cursor
.write_all(&crate::__private::CLOSED_ACCOUNT_DISCRIMINATOR)
.map_err(|_| ErrorCode::AccountDidNotSerialize)?;
Ok(())
}
2 changes: 2 additions & 0 deletions lang/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub enum ErrorCode {
ConstraintAssociated,
#[msg("An associated init constraint was violated")]
ConstraintAssociatedInit,
#[msg("A close constraint was violated")]
ConstraintClose,

// Accounts.
#[msg("The account discriminator was already set on this account")]
Expand Down
11 changes: 10 additions & 1 deletion lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ extern crate self as anchor_lang;

use bytemuck::{Pod, Zeroable};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::io::Write;

mod account_info;
mod boxed;
mod common;
mod context;
mod cpi_account;
mod cpi_state;
Expand Down Expand Up @@ -92,7 +94,13 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
/// should be done here.
pub trait AccountsExit<'info>: ToAccountMetas + ToAccountInfos<'info> {
/// `program_id` is the currently executing program.
fn exit(&self, program_id: &Pubkey) -> solana_program::entrypoint::ProgramResult;
fn exit(&self, program_id: &Pubkey) -> ProgramResult;
}

/// The close procedure to initiate garabage collection of an account, allowing
/// one to retrieve the rent exemption.
pub trait AccountsClose<'info>: ToAccountInfos<'info> {
fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult;
}

/// A data structure of accounts providing a one time deserialization upon
Expand Down Expand Up @@ -275,4 +283,5 @@ pub mod __private {
}

pub use crate::state::PROGRAM_STATE_SEED;
pub const CLOSED_ACCOUNT_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255];
}
9 changes: 8 additions & 1 deletion lang/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::error::ErrorCode;
use crate::{
Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas, ZeroCopy,
Accounts, AccountsClose, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos,
ToAccountMetas, ZeroCopy,
};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
Expand Down Expand Up @@ -175,6 +176,12 @@ impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
}
}

impl<'info, T: ZeroCopy> AccountsClose<'info> for Loader<'info, T> {
fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
crate::common::close(self.to_account_info(), sol_destination)
}
}

impl<'info, T: ZeroCopy> ToAccountMetas for Loader<'info, T> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
Expand Down
12 changes: 10 additions & 2 deletions lang/src/program_account.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AccountsInit, CpiAccount,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, AccountsInit,
CpiAccount, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
Expand Down Expand Up @@ -120,6 +120,14 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info
}
}

impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsClose<'info>
for ProgramAccount<'info, T>
{
fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
crate::common::close(self.to_account_info(), sol_destination)
}
}

impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for ProgramAccount<'info, T>
{
Expand Down
17 changes: 16 additions & 1 deletion lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo,
CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
ConstraintState, Field, Ty,
Expand Down Expand Up @@ -50,6 +50,7 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
executable,
state,
associated,
close,
} = c_group.clone();

let mut constraints = Vec::new();
Expand Down Expand Up @@ -94,6 +95,9 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
if let Some(c) = state {
constraints.push(Constraint::State(c));
}
if let Some(c) = close {
constraints.push(Constraint::Close(c));
}
constraints
}

Expand All @@ -111,6 +115,7 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
Constraint::Executable(c) => generate_constraint_executable(f, c),
Constraint::State(c) => generate_constraint_state(f, c),
Constraint::AssociatedGroup(c) => generate_constraint_associated(f, c),
Constraint::Close(c) => generate_constraint_close(f, c),
}
}

Expand All @@ -126,6 +131,16 @@ pub fn generate_constraint_init(_f: &Field, _c: &ConstraintInit) -> proc_macro2:
quote! {}
}

pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream {
let field = &f.ident;
let target = &c.sol_dest;
quote! {
if #field.to_account_info().key == #target.to_account_info().key {
return Err(anchor_lang::__private::ErrorCode::ConstraintClose.into());
}
}
}

pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::TokenStream {
let ident = &f.ident;
quote! {
Expand Down
20 changes: 15 additions & 5 deletions lang/syn/src/codegen/accounts/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,21 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
}
AccountField::Field(f) => {
let ident = &f.ident;
match f.constraints.is_mutable() {
false => quote! {},
true => quote! {
anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
},
if f.constraints.is_close() {
let close_target = &f.constraints.close.as_ref().unwrap().sol_dest;
quote! {
anchor_lang::AccountsClose::close(
&self.#ident,
self.#close_target.to_account_info(),
)?;
}
} else {
match f.constraints.is_mutable() {
false => quote! {},
true => quote! {
anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
},
}
}
}
})
Expand Down
Loading

0 comments on commit 24dd034

Please sign in to comment.