Skip to content
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: 1 addition & 1 deletion list-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ num-derive = "0.4.2"
num_enum = "0.7.5"
num-traits = "0.2.19"
solana-program-error = "3.0.0"
spl-pod = { version = "0.7.2", path = "../pod" }
spl-pod = { version = "0.7.2", path = "../pod", features = ["bytemuck"] }
thiserror = "2.0.18"

[dev-dependencies]
Expand Down
9 changes: 5 additions & 4 deletions pod/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ license = "Apache-2.0"
edition = "2021"

[features]
bytemuck = ["dep:bytemuck", "dep:bytemuck_derive", "solana-address/bytemuck"]
serde = ["dep:serde", "solana-address/decode"]
borsh = ["dep:borsh", "solana-address/borsh"]
wincode = ["dep:wincode"]

[dependencies]
borsh = { version = "1.5.7", features = ["derive", "unstable__schema"], optional = true }
bytemuck = { version = "1.23.2" }
bytemuck_derive = { version = "1.10.1" }
bytemuck = { version = "1.23.2", optional = true }
bytemuck_derive = { version = "1.10.1", optional = true }
serde = { version = "1.0.228", optional = true, features = ["derive"] }
solana-address = { version = "2.2.0", features = ["bytemuck"] }
solana-address = { version = "2.2.0", features = ["copy"] }
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously provided transitively by bytemuck feature, now needs it explicitly added

solana-program-error = "3.0.0"
solana-program-option = "3.0.0"
wincode = { version = "0.4.4", features = ["derive"], optional = true }
Expand All @@ -26,7 +27,7 @@ wincode = { version = "0.4.4", features = ["derive"], optional = true }
base64 = { version = "0.22.1" }
serde_json = "1.0.145"
solana-address = { version = "2.2.0", features = ["decode"] }
spl-pod = { path = ".", features = ["wincode"] }
spl-pod = { path = ".", features = ["bytemuck", "wincode"] }
test-case = "3.3.1"

[lib]
Expand Down
1 change: 1 addition & 0 deletions pod/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Crate containing `Pod` types and `bytemuck` utilities used in SPL

#[cfg(feature = "bytemuck")]
pub mod bytemuck;
pub mod option;
pub mod optional_keys;
Expand Down
59 changes: 54 additions & 5 deletions pod/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
//! [`Option<NonZeroU64>`](https://doc.rust-lang.org/std/num/type.NonZeroU64.html)
//! and provide the same memory layout optimization.

#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, Zeroable};
use {
bytemuck::{Pod, Zeroable},
solana_address::{Address, ADDRESS_BYTES},
solana_program_error::ProgramError,
solana_program_option::COption,
Expand All @@ -17,7 +18,7 @@ use {
///
/// This trait is used to indicate that a type can be `None` according to a
/// specific value.
pub trait Nullable: PartialEq + Pod + Sized {
pub trait Nullable: PartialEq + Sized {
/// Value that represents `None` for the type.
const NONE: Self;

Expand Down Expand Up @@ -77,19 +78,39 @@ impl<T: Nullable> PodOption<T> {
Some(&mut self.0)
}
}

/// Maps a `PodOption<T>` to an `Option<T>` by copying the contents of the option.
#[inline]
pub fn copied(&self) -> Option<T>
where
T: Copy,
{
self.as_ref().copied()
}

/// Maps a `PodOption<T>` to an `Option<T>` by cloning the contents of the option.
#[inline]
pub fn cloned(&self) -> Option<T>
where
T: Clone,
{
self.as_ref().cloned()
}
}

/// ## Safety
///
/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
/// data representation.
unsafe impl<T: Nullable> Pod for PodOption<T> {}
#[cfg(feature = "bytemuck")]
unsafe impl<T: Nullable + Pod> Pod for PodOption<T> {}

/// ## Safety
///
/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
/// data representation.
unsafe impl<T: Nullable> Zeroable for PodOption<T> {}
#[cfg(feature = "bytemuck")]
unsafe impl<T: Nullable + Zeroable> Zeroable for PodOption<T> {}

impl<T: Nullable> From<T> for PodOption<T> {
fn from(value: T) -> Self {
Expand Down Expand Up @@ -128,9 +149,12 @@ impl Nullable for Address {

#[cfg(test)]
mod tests {
use {super::*, crate::bytemuck::pod_slice_from_bytes};
use super::*;
#[cfg(feature = "bytemuck")]
use crate::bytemuck::pod_slice_from_bytes;
const ID: Address = Address::from_str_const("TestSysvar111111111111111111111111111111111");

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_option_address() {
let some_address = PodOption::from(ID);
Expand Down Expand Up @@ -185,4 +209,29 @@ mod tests {
let def = PodOption::<Address>::default();
assert_eq!(def, None.try_into().unwrap());
}

#[test]
fn test_copied() {
let some_address = PodOption::from(ID);
assert_eq!(some_address.copied(), Some(ID));

let none_address = PodOption::from(Address::NONE);
assert_eq!(none_address.copied(), None);
}

#[derive(Clone, Debug, PartialEq)]
struct TestNonCopyNullable([u8; 4]);

impl Nullable for TestNonCopyNullable {
const NONE: Self = Self([0u8; 4]);
}

#[test]
fn test_cloned_with_non_copy_nullable() {
let some = PodOption::from(TestNonCopyNullable([1, 2, 3, 4]));
assert_eq!(some.cloned(), Some(TestNonCopyNullable([1, 2, 3, 4])));

let none = PodOption::from(TestNonCopyNullable::NONE);
assert_eq!(none.cloned(), None);
}
}
17 changes: 9 additions & 8 deletions pod/src/optional_keys.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
//! Optional addresses that can be used a `Pod`s
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use {
bytemuck_derive::{Pod, Zeroable},
solana_address::Address,
solana_program_error::ProgramError,
solana_program_option::COption,
};
#[cfg(feature = "bytemuck")]
use bytemuck_derive::{Pod, Zeroable};
#[cfg(feature = "serde")]
use {
core::{convert::TryFrom, fmt, str::FromStr},
serde::de::{Error, Unexpected, Visitor},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
use {solana_address::Address, solana_program_error::ProgramError, solana_program_option::COption};

/// A Pubkey that encodes `None` as all `0`, meant to be usable as a `Pod` type,
/// similar to all `NonZero*` number types from the `bytemuck` library.
#[cfg_attr(
feature = "borsh",
derive(BorshDeserialize, BorshSerialize, BorshSchema)
)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct OptionalNonZeroPubkey(pub Address);
impl TryFrom<Option<Address>> for OptionalNonZeroPubkey {
Expand Down Expand Up @@ -129,8 +127,11 @@ impl<'de> Deserialize<'de> for OptionalNonZeroPubkey {

#[cfg(test)]
mod tests {
use {super::*, crate::bytemuck::pod_from_bytes, solana_address::ADDRESS_BYTES};
#[cfg(feature = "bytemuck")]
use crate::bytemuck::pod_from_bytes;
use {super::*, solana_address::ADDRESS_BYTES};

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_non_zero_option() {
assert_eq!(
Expand Down
32 changes: 24 additions & 8 deletions pod/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! primitive types that can be used in `Pod`s
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
#[cfg(feature = "bytemuck")]
use bytemuck_derive::{Pod, Zeroable};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand All @@ -12,7 +13,8 @@ use wincode::{SchemaRead, SchemaWrite};
#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "bool", into = "bool"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodBool(pub u8);
impl PodBool {
Expand Down Expand Up @@ -76,7 +78,8 @@ macro_rules! impl_int_conversion {
#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "u16", into = "u16"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodU16(pub [u8; 2]);
impl_int_conversion!(PodU16, u16);
Expand All @@ -86,7 +89,8 @@ impl_int_conversion!(PodU16, u16);
#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "i16", into = "i16"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodI16(pub [u8; 2]);
impl_int_conversion!(PodI16, i16);
Expand All @@ -100,7 +104,8 @@ impl_int_conversion!(PodI16, i16);
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "u32", into = "u32"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodU32(pub [u8; 4]);
impl_int_conversion!(PodU32, u32);
Expand All @@ -114,7 +119,8 @@ impl_int_conversion!(PodU32, u32);
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "u64", into = "u64"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodU64(pub [u8; 8]);
impl_int_conversion!(PodU64, u64);
Expand All @@ -124,7 +130,8 @@ impl_int_conversion!(PodU64, u64);
#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "i64", into = "i64"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodI64([u8; 8]);
impl_int_conversion!(PodI64, i64);
Expand All @@ -138,7 +145,8 @@ impl_int_conversion!(PodI64, i64);
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "u128", into = "u128"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(transparent)]
pub struct PodU128(pub [u8; 16]);
impl_int_conversion!(PodU128, u128);
Expand Down Expand Up @@ -172,8 +180,11 @@ impl_usize_conversion!(PodU128, u128);

#[cfg(test)]
mod tests {
use {super::*, crate::bytemuck::pod_from_bytes};
use super::*;
#[cfg(feature = "bytemuck")]
use crate::bytemuck::pod_from_bytes;

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_bool() {
assert!(pod_from_bytes::<PodBool>(&[]).is_err());
Expand Down Expand Up @@ -201,6 +212,7 @@ mod tests {
assert_eq!(pod_true, deserialized_true);
}

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_u16() {
assert!(pod_from_bytes::<PodU16>(&[]).is_err());
Expand All @@ -219,6 +231,7 @@ mod tests {
assert_eq!(pod_u16, deserialized);
}

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_i16() {
assert!(pod_from_bytes::<PodI16>(&[]).is_err());
Expand All @@ -242,6 +255,7 @@ mod tests {
assert_eq!(pod_i16, deserialized);
}

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_u64() {
assert!(pod_from_bytes::<PodU64>(&[]).is_err());
Expand All @@ -263,6 +277,7 @@ mod tests {
assert_eq!(pod_u64, deserialized);
}

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_i64() {
assert!(pod_from_bytes::<PodI64>(&[]).is_err());
Expand All @@ -286,6 +301,7 @@ mod tests {
assert_eq!(pod_i64, deserialized);
}

#[cfg(feature = "bytemuck")]
#[test]
fn test_pod_u128() {
assert!(pod_from_bytes::<PodU128>(&[]).is_err());
Expand Down
2 changes: 1 addition & 1 deletion tlv-account-resolution/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ solana-pubkey = { version = "3.0.0", features = ["curve25519"] }
spl-discriminator = { version = "0.5.1", path = "../discriminator" }
spl-list-view = { version = "0.1.0", path = "../list-view" }
spl-program-error = { version = "0.8.0", path = "../program-error" }
spl-pod = { version = "0.7.1", path = "../pod" }
spl-pod = { version = "0.7.1", path = "../pod", features = ["bytemuck"] }
spl-type-length-value = { version = "0.9.0", path = "../type-length-value" }
thiserror = "2.0"

Expand Down
2 changes: 1 addition & 1 deletion type-length-value/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ solana-msg = "3.0.0"
solana-program-error = "3.0.0"
spl-discriminator = { version = "0.5.1", path = "../discriminator" }
spl-type-length-value-derive = { version = "0.2", path = "../type-length-value-derive", optional = true }
spl-pod = { version = "0.7.1", path = "../pod" }
spl-pod = { version = "0.7.1", path = "../pod", features = ["bytemuck"] }
thiserror = "2.0"

[lib]
Expand Down
Loading