Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The minor version will be incremented upon a breaking change and the patch versi

### Fixes

- lang: Avoid an unnecessary stack allocation when snapshotting CPI return data ([#4931](https://github.com/otter-sec/anchor/pull/4931)).
- spl: Fix `anchor-spl` failing to build with only the `metadata` feature ([#4742](https://github.com/solana-foundation/anchor/pull/4742)).
- client: Fix ignored commitment level ([#4666](https://github.com/solana-foundation/anchor/pull/4666)).
- lang: Remove cloning `AccountInfo` to read lamports in `init_if_needed` codegen ([#4675](https://github.com/solana-foundation/anchor/pull/4675)).
Expand Down
6 changes: 3 additions & 3 deletions lang/attribute/program/src/declare_program/mods/cpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,16 @@ fn gen_cpi_instructions(idl: &Idl) -> proc_macro2::TokenStream {

fn gen_cpi_return_type() -> proc_macro2::TokenStream {
quote! {
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Return<T> {
phantom: ::std::marker::PhantomData<T>,
program_id: anchor_lang::solana_program::pubkey::Pubkey,
return_data: anchor_lang::__private::CpiReturnData,
return_data: Option<anchor_lang::__private::CpiReturnData>,
}

impl<T: AnchorDeserialize> Return<T> {
pub fn get(&self) -> T {
self.return_data.get(self.program_id)
self.return_data.as_ref().unwrap().get(self.program_id)
}

/// Read return data without validating the program_id.
Expand Down
39 changes: 12 additions & 27 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,52 +655,37 @@ pub mod __private {
impl<T> IsSameType<T> for T {}

#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct CpiReturnData {
program_id: Option<Pubkey>,
data_len: usize,
data: [u8; crate::solana_program::program::MAX_RETURN_DATA],
program_id: Pubkey,
data: Vec<u8>,
}

impl CpiReturnData {
#[doc(hidden)]
pub fn new(return_data: Option<(Pubkey, Vec<u8>)>) -> Self {
let mut snapshot = Self {
program_id: None,
data_len: 0,
data: [0u8; crate::solana_program::program::MAX_RETURN_DATA],
};

if let Some((program_id, data)) = return_data {
let data_len = data.len();
snapshot.data[..data_len].copy_from_slice(&data);
snapshot.program_id = Some(program_id);
snapshot.data_len = data_len;
}

snapshot
pub fn new(program_id: Pubkey, data: Vec<u8>) -> Self {
CpiReturnData { program_id, data }
}

#[doc(hidden)]
pub fn snapshot() -> Self {
Self::new(crate::solana_program::program::get_return_data())
pub fn snapshot() -> Option<Self> {
let (program_id, data) = crate::solana_program::program::get_return_data()?;
Some(Self::new(program_id, data))
}

#[doc(hidden)]
pub fn get<T: crate::AnchorDeserialize>(&self, expected_program_id: Pubkey) -> T {
let program_id = self.program_id.unwrap();
if program_id != expected_program_id {
if self.program_id != expected_program_id {
crate::solana_program::log::sol_log("CPI return data program_id mismatch");
panic!();
}

T::try_from_slice(&self.data[..self.data_len]).unwrap()
T::try_from_slice(&self.data).unwrap()
}

#[doc(hidden)]
pub fn return_data(&self) -> Option<(Pubkey, &[u8])> {
self.program_id
.map(|program_id| (program_id, &self.data[..self.data_len]))
pub fn return_data(&self) -> (Pubkey, &[u8]) {
(self.program_id, &self.data)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions lang/syn/src/codegen/program/cpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
use ::std::marker::PhantomData;


#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Return<T> {
phantom: ::std::marker::PhantomData<T>,
program_id: anchor_lang::solana_program::pubkey::Pubkey,
return_data: anchor_lang::__private::CpiReturnData,
return_data: Option<anchor_lang::__private::CpiReturnData>,
}

impl<T: AnchorDeserialize> Return<T> {
pub fn get(&self) -> T {
self.return_data.get(self.program_id)
self.return_data.as_ref().unwrap().get(self.program_id)
}

/// Read return data without validating the program_id.
Expand Down
10 changes: 5 additions & 5 deletions lang/tests/cpi_return_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ fn test_cpi_return_data_snapshot_keeps_original_bytes() {
let program_id = Pubkey::new_unique();
let original = 10u64;
let original_bytes = borsh::to_vec(&original).unwrap();
let snapshot = CpiReturnData::new(Some((program_id, original_bytes.clone())));
let snapshot = CpiReturnData::new(program_id, original_bytes.clone());

let spoofed = 999u64;
let spoofed_bytes = borsh::to_vec(&spoofed).unwrap();
let later = CpiReturnData::new(Some((program_id, spoofed_bytes.clone())));
let later = CpiReturnData::new(program_id, spoofed_bytes.clone());

let (snapshot_program_id, snapshot_bytes) = snapshot.return_data().unwrap();
let (snapshot_program_id, snapshot_bytes) = snapshot.return_data();
assert_eq!(snapshot_program_id, program_id);
assert_eq!(snapshot_bytes, original_bytes.as_slice());

let (later_program_id, later_bytes) = later.return_data().unwrap();
let (later_program_id, later_bytes) = later.return_data();
assert_eq!(later_program_id, program_id);
assert_eq!(later_bytes, spoofed_bytes.as_slice());

Expand All @@ -29,7 +29,7 @@ fn test_cpi_return_data_snapshot_rejects_program_id_mismatch() {
let program_id = Pubkey::new_unique();
let other_program_id = Pubkey::new_unique();
let value = 10u64;
let snapshot = CpiReturnData::new(Some((other_program_id, borsh::to_vec(&value).unwrap())));
let snapshot = CpiReturnData::new(other_program_id, borsh::to_vec(&value).unwrap());

let _ = snapshot.get::<u64>(program_id);
}
Loading