Skip to content

Commit ec6a4b6

Browse files
committed
PodSliceError ergonomics change
1 parent 788a656 commit ec6a4b6

File tree

11 files changed

+142
-76
lines changed

11 files changed

+142
-76
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pod/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ bytemuck_derive = { version = "1.10.1" }
1818
num-derive = "0.4"
1919
num_enum = "0.7"
2020
num-traits = "0.2"
21+
pinocchio = "0.9.2"
2122
serde = { version = "1.0.228", optional = true }
2223
solana-program-error = "3.0.0"
2324
solana-program-option = "3.0.0"

pod/src/bytemuck.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! wrappers for `bytemuck` functions
22
3-
use {bytemuck::Pod, solana_program_error::ProgramError};
3+
use crate::error::PodSliceError;
4+
use bytemuck::Pod;
45

56
/// On-chain size of a `Pod` type
67
pub const fn pod_get_packed_len<T: Pod>() -> usize {
@@ -13,38 +14,38 @@ pub fn pod_bytes_of<T: Pod>(t: &T) -> &[u8] {
1314
}
1415

1516
/// Convert a slice of bytes into a `Pod` (zero copy)
16-
pub fn pod_from_bytes<T: Pod>(bytes: &[u8]) -> Result<&T, ProgramError> {
17-
bytemuck::try_from_bytes(bytes).map_err(|_| ProgramError::InvalidArgument)
17+
pub fn pod_from_bytes<T: Pod>(bytes: &[u8]) -> Result<&T, PodSliceError> {
18+
bytemuck::try_from_bytes(bytes).map_err(PodSliceError::from)
1819
}
1920

2021
/// Maybe convert a slice of bytes into a `Pod` (zero copy)
2122
///
2223
/// Returns `None` if the slice is empty, or else `Err` if input length is not
2324
/// equal to `pod_get_packed_len::<T>()`.
2425
/// This function exists primarily because `Option<T>` is not a `Pod`.
25-
pub fn pod_maybe_from_bytes<T: Pod>(bytes: &[u8]) -> Result<Option<&T>, ProgramError> {
26+
pub fn pod_maybe_from_bytes<T: Pod>(bytes: &[u8]) -> Result<Option<&T>, PodSliceError> {
2627
if bytes.is_empty() {
2728
Ok(None)
2829
} else {
2930
bytemuck::try_from_bytes(bytes)
3031
.map(Some)
31-
.map_err(|_| ProgramError::InvalidArgument)
32+
.map_err(PodSliceError::from)
3233
}
3334
}
3435

3536
/// Convert a slice of bytes into a mutable `Pod` (zero copy)
36-
pub fn pod_from_bytes_mut<T: Pod>(bytes: &mut [u8]) -> Result<&mut T, ProgramError> {
37-
bytemuck::try_from_bytes_mut(bytes).map_err(|_| ProgramError::InvalidArgument)
37+
pub fn pod_from_bytes_mut<T: Pod>(bytes: &mut [u8]) -> Result<&mut T, PodSliceError> {
38+
bytemuck::try_from_bytes_mut(bytes).map_err(PodSliceError::from)
3839
}
3940

4041
/// Convert a slice of bytes into a `Pod` slice (zero copy)
41-
pub fn pod_slice_from_bytes<T: Pod>(bytes: &[u8]) -> Result<&[T], ProgramError> {
42-
bytemuck::try_cast_slice(bytes).map_err(|_| ProgramError::InvalidArgument)
42+
pub fn pod_slice_from_bytes<T: Pod>(bytes: &[u8]) -> Result<&[T], PodSliceError> {
43+
bytemuck::try_cast_slice(bytes).map_err(PodSliceError::from)
4344
}
4445

4546
/// Convert a slice of bytes into a mutable `Pod` slice (zero copy)
46-
pub fn pod_slice_from_bytes_mut<T: Pod>(bytes: &mut [u8]) -> Result<&mut [T], ProgramError> {
47-
bytemuck::try_cast_slice_mut(bytes).map_err(|_| ProgramError::InvalidArgument)
47+
pub fn pod_slice_from_bytes_mut<T: Pod>(bytes: &mut [u8]) -> Result<&mut [T], PodSliceError> {
48+
bytemuck::try_cast_slice_mut(bytes).map_err(PodSliceError::from)
4849
}
4950

5051
/// Convert a `Pod` slice into a single slice of bytes

pod/src/error.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Error types
2+
use pinocchio::program_error::ProgramError as PinocchioProgramError;
23
use {
4+
bytemuck::PodCastError,
35
solana_program_error::{ProgramError, ToStr},
46
std::num::TryFromIntError,
57
};
@@ -25,6 +27,12 @@ pub enum PodSliceError {
2527
/// Provided byte buffer too large for expected type
2628
#[error("Provided byte buffer too large for expected type")]
2729
BufferTooLarge,
30+
/// An invalid argument was provided
31+
#[error("An invalid argument was provided")]
32+
InvalidArgument,
33+
/// A `PodCast` operation from `bytemuck` failed
34+
#[error("A `PodCast` operation from `bytemuck` failed")]
35+
PodCast,
2836
/// An integer conversion failed because the value was out of range for the target type
2937
#[error("An integer conversion failed because the value was out of range for the target type")]
3038
ValueOutOfRange,
@@ -42,13 +50,64 @@ impl ToStr for PodSliceError {
4250
PodSliceError::CalculationFailure => "Error in checked math operation",
4351
PodSliceError::BufferTooSmall => "Provided byte buffer too small for expected type",
4452
PodSliceError::BufferTooLarge => "Provided byte buffer too large for expected type",
45-
PodSliceError::ValueOutOfRange => "An integer conversion failed because the value was out of range for the target type"
53+
PodSliceError::InvalidArgument => "An invalid argument was provided",
54+
PodSliceError::PodCast => "A `PodCast` operation from `bytemuck` failed",
55+
PodSliceError::ValueOutOfRange => "An integer conversion failed because the value was out of range for the target type",
4656
}
4757
}
4858
}
4959

60+
impl From<PodCastError> for PodSliceError {
61+
fn from(_: PodCastError) -> Self {
62+
PodSliceError::PodCast
63+
}
64+
}
65+
5066
impl From<TryFromIntError> for PodSliceError {
5167
fn from(_: TryFromIntError) -> Self {
5268
PodSliceError::ValueOutOfRange
5369
}
5470
}
71+
72+
impl From<PodSliceError> for PinocchioProgramError {
73+
fn from(e: PodSliceError) -> Self {
74+
let solana_err: ProgramError = e.into();
75+
u64::from(solana_err).into()
76+
}
77+
}
78+
79+
#[cfg(test)]
80+
mod test {
81+
use super::*;
82+
use crate::list::ListView;
83+
use pinocchio::program_error::ProgramError as PinocchioProgramError;
84+
85+
fn raises_solana_err() -> Result<(), ProgramError> {
86+
ListView::<u8>::size_of(usize::MAX)?; // raises err
87+
Ok(())
88+
}
89+
90+
fn raises_pino_err() -> Result<(), PinocchioProgramError> {
91+
ListView::<u8>::size_of(usize::MAX)?; // raises err
92+
Ok(())
93+
}
94+
95+
#[test]
96+
fn test_from_pod_slice_error_for_solana_program_error() {
97+
let result = raises_solana_err();
98+
assert!(result.is_err());
99+
let solana_err = result.unwrap_err();
100+
let expected_err: ProgramError = PodSliceError::CalculationFailure.into();
101+
assert_eq!(solana_err, expected_err);
102+
}
103+
104+
#[test]
105+
fn test_from_pod_slice_error_for_pinocchio_program_error() {
106+
let result = raises_pino_err();
107+
assert!(result.is_err());
108+
let pinocchio_err = result.unwrap_err();
109+
let expected_solana_err: ProgramError = PodSliceError::CalculationFailure.into();
110+
let expected_pinocchio_err: PinocchioProgramError = u64::from(expected_solana_err).into();
111+
assert_eq!(pinocchio_err, expected_pinocchio_err);
112+
}
113+
}

pod/src/list/list_trait.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use {
2-
crate::{list::ListView, pod_length::PodLength},
2+
crate::{error::PodSliceError, list::ListView, pod_length::PodLength},
33
bytemuck::Pod,
4-
solana_program_error::ProgramError,
54
std::ops::Deref,
65
};
76

@@ -17,12 +16,12 @@ pub trait List: Deref<Target = [Self::Item]> {
1716
fn capacity(&self) -> usize;
1817

1918
/// Returns the number of **bytes currently occupied** by the live elements
20-
fn bytes_used(&self) -> Result<usize, ProgramError> {
19+
fn bytes_used(&self) -> Result<usize, PodSliceError> {
2120
ListView::<Self::Item, Self::Length>::size_of(self.len())
2221
}
2322

2423
/// Returns the number of **bytes reserved** by the entire backing buffer.
25-
fn bytes_allocated(&self) -> Result<usize, ProgramError> {
24+
fn bytes_allocated(&self) -> Result<usize, PodSliceError> {
2625
ListView::<Self::Item, Self::Length>::size_of(self.capacity())
2726
}
2827
}

pod/src/list/list_view.rs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use {
1111
primitives::PodU32,
1212
},
1313
bytemuck::Pod,
14-
solana_program_error::ProgramError,
1514
std::{
1615
marker::PhantomData,
1716
mem::{align_of, size_of},
@@ -51,17 +50,17 @@ struct Layout {
5150
impl<T: Pod, L: PodLength> ListView<T, L> {
5251
/// Calculate the total byte size for a `ListView` holding `num_items`.
5352
/// This includes the length prefix, padding, and data.
54-
pub fn size_of(num_items: usize) -> Result<usize, ProgramError> {
53+
pub fn size_of(num_items: usize) -> Result<usize, PodSliceError> {
5554
let header_padding = Self::header_padding()?;
5655
size_of::<T>()
5756
.checked_mul(num_items)
5857
.and_then(|curr| curr.checked_add(size_of::<L>()))
5958
.and_then(|curr| curr.checked_add(header_padding))
60-
.ok_or_else(|| PodSliceError::CalculationFailure.into())
59+
.ok_or(PodSliceError::CalculationFailure)
6160
}
6261

6362
/// Unpack a read-only buffer into a `ListViewReadOnly`
64-
pub fn unpack(buf: &[u8]) -> Result<ListViewReadOnly<T, L>, ProgramError> {
63+
pub fn unpack(buf: &[u8]) -> Result<ListViewReadOnly<T, L>, PodSliceError> {
6564
let layout = Self::calculate_layout(buf.len())?;
6665

6766
// Slice the buffer to get the length prefix and the data.
@@ -79,7 +78,7 @@ impl<T: Pod, L: PodLength> ListView<T, L> {
7978
let capacity = data.len();
8079

8180
if (*length).into() > capacity {
82-
return Err(PodSliceError::BufferTooSmall.into());
81+
return Err(PodSliceError::BufferTooSmall);
8382
}
8483

8584
Ok(ListViewReadOnly {
@@ -90,24 +89,24 @@ impl<T: Pod, L: PodLength> ListView<T, L> {
9089
}
9190

9291
/// Unpack the mutable buffer into a mutable `ListViewMut`
93-
pub fn unpack_mut(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
92+
pub fn unpack_mut(buf: &mut [u8]) -> Result<ListViewMut<T, L>, PodSliceError> {
9493
let view = Self::build_mut_view(buf)?;
9594
if (*view.length).into() > view.capacity {
96-
return Err(PodSliceError::BufferTooSmall.into());
95+
return Err(PodSliceError::BufferTooSmall);
9796
}
9897
Ok(view)
9998
}
10099

101100
/// Initialize a buffer: sets `length = 0` and returns a mutable `ListViewMut`.
102-
pub fn init(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
101+
pub fn init(buf: &mut [u8]) -> Result<ListViewMut<T, L>, PodSliceError> {
103102
let view = Self::build_mut_view(buf)?;
104103
*view.length = L::try_from(0)?;
105104
Ok(view)
106105
}
107106

108107
/// Internal helper to build a mutable view without validation or initialization.
109108
#[inline]
110-
fn build_mut_view(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
109+
fn build_mut_view(buf: &mut [u8]) -> Result<ListViewMut<T, L>, PodSliceError> {
111110
let layout = Self::calculate_layout(buf.len())?;
112111

113112
// Split the buffer to get the length prefix and the data.
@@ -133,13 +132,13 @@ impl<T: Pod, L: PodLength> ListView<T, L> {
133132

134133
/// Calculate the byte ranges for the length and data sections of the buffer
135134
#[inline]
136-
fn calculate_layout(buf_len: usize) -> Result<Layout, ProgramError> {
135+
fn calculate_layout(buf_len: usize) -> Result<Layout, PodSliceError> {
137136
let len_field_end = size_of::<L>();
138137
let header_padding = Self::header_padding()?;
139138
let data_start = len_field_end.saturating_add(header_padding);
140139

141140
if buf_len < data_start {
142-
return Err(PodSliceError::BufferTooSmall.into());
141+
return Err(PodSliceError::BufferTooSmall);
143142
}
144143

145144
Ok(Layout {
@@ -153,10 +152,10 @@ impl<T: Pod, L: PodLength> ListView<T, L> {
153152
/// The goal is to ensure that the data field `T` starts at a memory offset
154153
/// that is a multiple of its alignment requirement.
155154
#[inline]
156-
fn header_padding() -> Result<usize, ProgramError> {
155+
fn header_padding() -> Result<usize, PodSliceError> {
157156
// Enforce that the length prefix type `L` itself does not have alignment requirements
158157
if align_of::<L>() != 1 {
159-
return Err(ProgramError::InvalidArgument);
158+
return Err(PodSliceError::InvalidArgument);
160159
}
161160

162161
let length_size = size_of::<L>();
@@ -240,12 +239,12 @@ mod tests {
240239
// Case 1: Multiplication overflows.
241240
// `size_of::<u16>() * usize::MAX` will overflow.
242241
let err = ListView::<u16, PodU32>::size_of(usize::MAX).unwrap_err();
243-
assert_eq!(err, PodSliceError::CalculationFailure.into());
242+
assert_eq!(err, PodSliceError::CalculationFailure);
244243

245244
// Case 2: Multiplication does not overflow, but subsequent addition does.
246245
// `size_of::<u8>() * usize::MAX` does not overflow, but adding `size_of<L>` will.
247246
let err = ListView::<u8, PodU32>::size_of(usize::MAX).unwrap_err();
248-
assert_eq!(err, PodSliceError::CalculationFailure.into());
247+
assert_eq!(err, PodSliceError::CalculationFailure);
249248
}
250249

251250
#[test]
@@ -271,13 +270,13 @@ mod tests {
271270
let mut buf = [0u8; 100];
272271

273272
let err_size_of = ListView::<u8, TestPodU32>::size_of(10).unwrap_err();
274-
assert_eq!(err_size_of, ProgramError::InvalidArgument);
273+
assert_eq!(err_size_of, PodSliceError::InvalidArgument);
275274

276275
let err_unpack = ListView::<u8, TestPodU32>::unpack(&buf).unwrap_err();
277-
assert_eq!(err_unpack, ProgramError::InvalidArgument);
276+
assert_eq!(err_unpack, PodSliceError::InvalidArgument);
278277

279278
let err_init = ListView::<u8, TestPodU32>::init(&mut buf).unwrap_err();
280-
assert_eq!(err_init, ProgramError::InvalidArgument);
279+
assert_eq!(err_init, PodSliceError::InvalidArgument);
281280
}
282281

283282
#[test]
@@ -445,10 +444,10 @@ mod tests {
445444
let mut buf = vec![0u8; header_size - 1]; // 7 bytes
446445

447446
let err = ListView::<u64, PodU32>::unpack(&buf).unwrap_err();
448-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
447+
assert_eq!(err, PodSliceError::BufferTooSmall);
449448

450449
let err = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap_err();
451-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
450+
assert_eq!(err, PodSliceError::BufferTooSmall);
452451
}
453452

454453
#[test]
@@ -465,10 +464,10 @@ mod tests {
465464
buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
466465

467466
let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
468-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
467+
assert_eq!(err, PodSliceError::BufferTooSmall);
469468

470469
let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
471-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
470+
assert_eq!(err, PodSliceError::BufferTooSmall);
472471
}
473472

474473
#[test]
@@ -482,20 +481,20 @@ mod tests {
482481
// bytemuck::try_cast_slice returns an alignment error, which we map to InvalidArgument
483482

484483
let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
485-
assert_eq!(err, ProgramError::InvalidArgument);
484+
assert_eq!(err, PodSliceError::PodCast);
486485

487486
let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
488-
assert_eq!(err, ProgramError::InvalidArgument);
487+
assert_eq!(err, PodSliceError::PodCast);
489488
}
490489

491490
#[test]
492491
fn test_unpack_empty_buffer() {
493492
let mut buf = [];
494493
let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
495-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
494+
assert_eq!(err, PodSliceError::BufferTooSmall);
496495

497496
let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
498-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
497+
assert_eq!(err, PodSliceError::BufferTooSmall);
499498
}
500499

501500
#[test]
@@ -562,12 +561,12 @@ mod tests {
562561
// Header requires 4 bytes (size_of<PodU32>)
563562
let mut buf = vec![0u8; 3];
564563
let err = ListView::<u32, PodU32>::init(&mut buf).unwrap_err();
565-
assert_eq!(err, PodSliceError::BufferTooSmall.into());
564+
assert_eq!(err, PodSliceError::BufferTooSmall);
566565

567566
// With padding, header requires 8 bytes (4 for len, 4 for pad)
568567
let mut buf_padded = vec![0u8; 7];
569568
let err_padded = ListView::<u64, PodU32>::init(&mut buf_padded).unwrap_err();
570-
assert_eq!(err_padded, PodSliceError::BufferTooSmall.into());
569+
assert_eq!(err_padded, PodSliceError::BufferTooSmall);
571570
}
572571

573572
#[test]

0 commit comments

Comments
 (0)