diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0efd72c9..2f71fd76 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,9 +8,9 @@ on: env: JS_PACKAGES: "['type-length-value-js']" - SBPF_PROGRAM_PACKAGES: "['discriminator', 'generic-token', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']" - RUST_PACKAGES: "['discriminator', 'discriminator-derive', 'discriminator-syn', 'generic-token', 'generic-token-tests', 'pod', 'program-error', 'program-error-derive', 'tlv-account-resolution', 'type-length-value', 'type-length-value-derive', 'type-length-value-derive-test']" - WASM_PACKAGES: "['discriminator', 'generic-token', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']" + SBPF_PROGRAM_PACKAGES: "['discriminator', 'generic-token', 'list-view', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']" + RUST_PACKAGES: "['discriminator', 'discriminator-derive', 'discriminator-syn', 'generic-token', 'generic-token-tests', 'list-view', 'pod', 'program-error', 'program-error-derive', 'tlv-account-resolution', 'type-length-value', 'type-length-value-derive', 'type-length-value-derive-test']" + WASM_PACKAGES: "['discriminator', 'generic-token', 'list-view', 'pod', 'program-error', 'tlv-account-resolution', 'type-length-value']" jobs: set_env: diff --git a/.github/workflows/publish-rust.yml b/.github/workflows/publish-rust.yml index e370062f..5469c662 100644 --- a/.github/workflows/publish-rust.yml +++ b/.github/workflows/publish-rust.yml @@ -13,6 +13,7 @@ on: - discriminator-derive - discriminator-syn - generic-token + - list-view - pod - program-error - program-error-derive diff --git a/Cargo.lock b/Cargo.lock index 55f64d14..6df34411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,18 +159,18 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -1607,6 +1607,16 @@ dependencies = [ "test-case", ] +[[package]] +name = "spl-list-view" +version = "0.1.0" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "solana-program-error", + "spl-pod 0.7.2", +] + [[package]] name = "spl-pod" version = "0.7.1" @@ -1692,6 +1702,7 @@ dependencies = [ "solana-program-error", "solana-pubkey", "spl-discriminator 0.5.1", + "spl-list-view", "spl-pod 0.7.2", "spl-program-error", "spl-type-length-value 0.9.0", diff --git a/Cargo.toml b/Cargo.toml index 2d3e07f9..a18b107f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "discriminator-syn", "generic-token", "generic-token-tests", + "list-view", "pod", "program-error", "program-error-derive", diff --git a/list-view/Cargo.toml b/list-view/Cargo.toml new file mode 100644 index 00000000..ce5bc821 --- /dev/null +++ b/list-view/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "spl-list-view" +version = "0.1.0" +description = "ListView: a zero-copy, variable-length array view" +authors = ["Anza Maintainers "] +repository = "https://github.com/solana-program/libraries" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +bytemuck = "1.25.0" +solana-program-error = "3.0.0" +spl-pod = { version = "0.7.2", path = "../pod" } + +[dev-dependencies] +bytemuck_derive = "1.10.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/pod/src/list/mod.rs b/list-view/src/lib.rs similarity index 100% rename from pod/src/list/mod.rs rename to list-view/src/lib.rs diff --git a/pod/src/list/list_trait.rs b/list-view/src/list_trait.rs similarity index 86% rename from pod/src/list/list_trait.rs rename to list-view/src/list_trait.rs index 4973e075..b3731268 100644 --- a/pod/src/list/list_trait.rs +++ b/list-view/src/list_trait.rs @@ -1,8 +1,6 @@ use { - crate::{list::ListView, pod_length::PodLength}, - bytemuck::Pod, - core::ops::Deref, - solana_program_error::ProgramError, + crate::ListView, bytemuck::Pod, core::ops::Deref, solana_program_error::ProgramError, + spl_pod::pod_length::PodLength, }; /// A trait to abstract the shared, read-only behavior diff --git a/pod/src/list/list_view.rs b/list-view/src/list_view.rs similarity index 99% rename from pod/src/list/list_view.rs rename to list-view/src/list_view.rs index 21bae335..e45da26f 100644 --- a/pod/src/list/list_view.rs +++ b/list-view/src/list_view.rs @@ -1,15 +1,7 @@ //! `ListView`, a compact, zero-copy array wrapper. use { - crate::{ - bytemuck::{ - pod_from_bytes, pod_from_bytes_mut, pod_slice_from_bytes, pod_slice_from_bytes_mut, - }, - error::PodSliceError, - list::{list_view_mut::ListViewMut, list_view_read_only::ListViewReadOnly}, - pod_length::PodLength, - primitives::PodU32, - }, + crate::{list_view_mut::ListViewMut, list_view_read_only::ListViewReadOnly}, bytemuck::Pod, core::{ marker::PhantomData, @@ -17,6 +9,14 @@ use { ops::Range, }, solana_program_error::ProgramError, + spl_pod::{ + bytemuck::{ + pod_from_bytes, pod_from_bytes_mut, pod_slice_from_bytes, pod_slice_from_bytes_mut, + }, + error::PodSliceError, + pod_length::PodLength, + primitives::PodU32, + }, }; /// An API for interpreting a raw buffer (`&[u8]`) as a variable-length collection of Pod elements. @@ -185,11 +185,9 @@ impl ListView { mod tests { use { super::*, - crate::{ - list::List, - primitives::{PodU128, PodU16, PodU32, PodU64}, - }, + crate::List, bytemuck_derive::{Pod as DerivePod, Zeroable}, + spl_pod::primitives::{PodU128, PodU16, PodU32, PodU64}, }; #[test] diff --git a/pod/src/list/list_view_mut.rs b/list-view/src/list_view_mut.rs similarity index 98% rename from pod/src/list/list_view_mut.rs rename to list-view/src/list_view_mut.rs index 5a936e7a..7cdc3aed 100644 --- a/pod/src/list/list_view_mut.rs +++ b/list-view/src/list_view_mut.rs @@ -1,12 +1,11 @@ //! `ListViewMut`, a mutable, compact, zero-copy array wrapper. use { - crate::{ - error::PodSliceError, list::list_trait::List, pod_length::PodLength, primitives::PodU32, - }, + crate::list_trait::List, bytemuck::Pod, core::ops::{Deref, DerefMut}, solana_program_error::ProgramError, + spl_pod::{error::PodSliceError, pod_length::PodLength, primitives::PodU32}, }; #[derive(Debug)] @@ -82,11 +81,9 @@ impl List for ListViewMut<'_, T, L> { mod tests { use { super::*, - crate::{ - list::{List, ListView}, - primitives::{PodU16, PodU32, PodU64}, - }, + crate::{List, ListView}, bytemuck_derive::{Pod, Zeroable}, + spl_pod::primitives::{PodU16, PodU32, PodU64}, }; #[repr(C)] diff --git a/pod/src/list/list_view_read_only.rs b/list-view/src/list_view_read_only.rs similarity index 98% rename from pod/src/list/list_view_read_only.rs rename to list-view/src/list_view_read_only.rs index 211437a8..304f3be5 100644 --- a/pod/src/list/list_view_read_only.rs +++ b/list-view/src/list_view_read_only.rs @@ -1,9 +1,10 @@ //! `ListViewReadOnly`, a read-only, compact, zero-copy array wrapper. use { - crate::{list::list_trait::List, pod_length::PodLength, primitives::PodU32}, + crate::list_trait::List, bytemuck::Pod, core::ops::Deref, + spl_pod::{pod_length::PodLength, primitives::PodU32}, }; #[derive(Debug)] @@ -35,13 +36,13 @@ impl Deref for ListViewReadOnly<'_, T, L> { mod tests { use { super::*, - crate::{ - list::ListView, + crate::ListView, + bytemuck_derive::{Pod as DerivePod, Zeroable}, + core::mem::size_of, + spl_pod::{ pod_length::PodLength, primitives::{PodU32, PodU64}, }, - bytemuck_derive::{Pod as DerivePod, Zeroable}, - core::mem::size_of, }; #[repr(C, align(16))] diff --git a/pod/src/lib.rs b/pod/src/lib.rs index b9a26da1..f330e8de 100644 --- a/pod/src/lib.rs +++ b/pod/src/lib.rs @@ -2,12 +2,10 @@ pub mod bytemuck; pub mod error; -pub mod list; pub mod option; pub mod optional_keys; pub mod pod_length; pub mod primitives; -pub mod slice; // Export current sdk types for downstream users building with a different sdk // version diff --git a/pod/src/slice.rs b/pod/src/slice.rs deleted file mode 100644 index 8a6c01dc..00000000 --- a/pod/src/slice.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Special types for working with slices of `Pod`s - -use { - crate::{ - list::{ListView, ListViewMut, ListViewReadOnly}, - primitives::PodU32, - }, - bytemuck::Pod, - solana_program_error::ProgramError, -}; - -#[deprecated( - since = "0.6.0", - note = "This struct will be removed in the next major release (1.0.0). Please use `ListView` instead." -)] -/// Special type for using a slice of `Pod`s in a zero-copy way -#[allow(deprecated)] -pub struct PodSlice<'data, T: Pod> { - inner: ListViewReadOnly<'data, T, PodU32>, -} - -#[allow(deprecated)] -impl<'data, T: Pod> PodSlice<'data, T> { - /// Unpack the buffer into a slice - pub fn unpack<'a>(data: &'a [u8]) -> Result - where - 'a: 'data, - { - let inner = ListView::::unpack(data)?; - Ok(Self { inner }) - } - - /// Get the slice data - pub fn data(&self) -> &[T] { - let len = self.inner.len(); - &self.inner.data[..len] - } - - /// Get the amount of bytes used by `num_items` - pub fn size_of(num_items: usize) -> Result { - ListView::::size_of(num_items) - } -} - -#[deprecated( - since = "0.6.0", - note = "This struct will be removed in the next major release (1.0.0). Please use `ListView` instead." -)] -/// Special type for using a slice of mutable `Pod`s in a zero-copy way. -/// Uses `ListView` under the hood. -pub struct PodSliceMut<'data, T: Pod> { - inner: ListViewMut<'data, T, PodU32>, -} - -#[allow(deprecated)] -impl<'data, T: Pod> PodSliceMut<'data, T> { - /// Unpack the mutable buffer into a mutable slice - pub fn unpack<'a>(data: &'a mut [u8]) -> Result - where - 'a: 'data, - { - let inner = ListView::::unpack_mut(data)?; - Ok(Self { inner }) - } - - /// Unpack the mutable buffer into a mutable slice, and initialize the - /// slice to 0-length - pub fn init<'a>(data: &'a mut [u8]) -> Result - where - 'a: 'data, - { - let inner = ListView::::init(data)?; - Ok(Self { inner }) - } - - /// Add another item to the slice - pub fn push(&mut self, t: T) -> Result<(), ProgramError> { - self.inner.push(t) - } -} - -#[cfg(test)] -#[allow(deprecated)] -mod tests { - use { - super::*, - crate::{bytemuck::pod_slice_to_bytes, error::PodSliceError}, - bytemuck_derive::{Pod, Zeroable}, - }; - - #[repr(C)] - #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] - struct TestStruct { - test_field: u8, - test_pubkey: [u8; 32], - } - - const LENGTH_SIZE: usize = core::mem::size_of::(); - - #[test] - fn test_pod_slice() { - let test_field_bytes = [0]; - let test_pubkey_bytes = [1; 32]; - let len_bytes = [2, 0, 0, 0]; - - // Slice will contain 2 `TestStruct` - let mut data_bytes = [0; 66]; - data_bytes[0..1].copy_from_slice(&test_field_bytes); - data_bytes[1..33].copy_from_slice(&test_pubkey_bytes); - data_bytes[33..34].copy_from_slice(&test_field_bytes); - data_bytes[34..66].copy_from_slice(&test_pubkey_bytes); - - let mut pod_slice_bytes = [0; 70]; - pod_slice_bytes[0..4].copy_from_slice(&len_bytes); - pod_slice_bytes[4..70].copy_from_slice(&data_bytes); - - let pod_slice = PodSlice::::unpack(&pod_slice_bytes).unwrap(); - let pod_slice_data = pod_slice.data(); - - assert_eq!(pod_slice.inner.len(), 2); - assert_eq!(pod_slice_to_bytes(pod_slice.data()), data_bytes); - assert_eq!(pod_slice_data[0].test_field, test_field_bytes[0]); - assert_eq!(pod_slice_data[0].test_pubkey, test_pubkey_bytes); - assert_eq!(PodSlice::::size_of(1).unwrap(), 37); - } - - #[test] - fn test_pod_slice_buffer_too_large() { - // Length is 1. We pass one test struct with 6 trailing bytes to - // trigger BufferTooLarge. - let data_len = LENGTH_SIZE + core::mem::size_of::() + 6; - let mut pod_slice_bytes = vec![1; data_len]; - pod_slice_bytes[0..4].copy_from_slice(&[1, 0, 0, 0]); - let err = PodSlice::::unpack(&pod_slice_bytes) - .err() - .unwrap(); - assert!(matches!(err, ProgramError::InvalidArgument)); - } - - #[test] - fn test_pod_slice_buffer_larger_than_length_value() { - // If the buffer is longer than the u32 length value declares, it - // should still unpack successfully, as long as the length of the rest - // of the buffer can be divided by `size_of::`. - let length: u32 = 12; - let length_le = length.to_le_bytes(); - - // First set up the data to have room for extra items. - let data_len = PodSlice::::size_of(length as usize + 2).unwrap(); - let mut data = vec![0; data_len]; - - // Now write the bogus length - which is smaller - into the first 4 - // bytes. - data[..LENGTH_SIZE].copy_from_slice(&length_le); - - let pod_slice = PodSlice::::unpack(&data).unwrap(); - let pod_slice_len = pod_slice.inner.len() as u32; - let data = pod_slice.data(); - let data_vec = data.to_vec(); - - assert_eq!(pod_slice_len, length); - assert_eq!(data.len(), length as usize); - assert_eq!(data_vec.len(), length as usize); - } - - #[test] - fn test_pod_slice_buffer_too_small() { - // 1 `TestStruct` + length = 37 bytes - // we pass 36 to trigger BufferTooSmall - let pod_slice_bytes = [1; 36]; - let err = PodSlice::::unpack(&pod_slice_bytes) - .err() - .unwrap(); - assert!(matches!(err, ProgramError::InvalidArgument)); - } - - #[test] - fn test_pod_slice_buffer_shorter_than_length_value() { - // If the buffer is shorter than the u32 length value declares, we - // should get a BufferTooSmall error. - let length: u32 = 12; - let length_le = length.to_le_bytes(); - for num_items in 0..length { - // First set up the data to have `num_elements` items. - let data_len = PodSlice::::size_of(num_items as usize).unwrap(); - let mut data = vec![0; data_len]; - - // Now write the bogus length - which is larger - into the first 4 - // bytes. - data[..LENGTH_SIZE].copy_from_slice(&length_le); - - // Expect an error on unpacking. - let err = PodSlice::::unpack(&data).err().unwrap(); - assert_eq!( - err, - PodSliceError::BufferTooSmall.into(), - "Expected an `PodSliceError::BufferTooSmall` error" - ); - } - } - - #[test] - fn test_pod_slice_mut() { - // slice can fit 2 `TestStruct` - let mut pod_slice_bytes = [0; 70]; - // set length to 1, so we have room to push 1 more item - let len_bytes = [1, 0, 0, 0]; - pod_slice_bytes[0..4].copy_from_slice(&len_bytes); - - let mut pod_slice = PodSliceMut::::unpack(&mut pod_slice_bytes).unwrap(); - - assert_eq!(pod_slice.inner.len(), 1); - pod_slice.push(TestStruct::default()).unwrap(); - assert_eq!(pod_slice.inner.len(), 2); - - let err = pod_slice - .push(TestStruct::default()) - .expect_err("Expected an `PodSliceError::BufferTooSmall` error"); - assert_eq!(err, PodSliceError::BufferTooSmall.into()); - } -} diff --git a/tlv-account-resolution/Cargo.toml b/tlv-account-resolution/Cargo.toml index ad581a97..aa1e2f59 100644 --- a/tlv-account-resolution/Cargo.toml +++ b/tlv-account-resolution/Cargo.toml @@ -21,6 +21,7 @@ solana-instruction = { version = "3.0.0", features = ["std"] } solana-program-error = "3.0.0" 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-type-length-value = { version = "0.9.0", path = "../type-length-value" } diff --git a/tlv-account-resolution/src/state.rs b/tlv-account-resolution/src/state.rs index d247f9ce..cd0dcfb8 100644 --- a/tlv-account-resolution/src/state.rs +++ b/tlv-account-resolution/src/state.rs @@ -7,10 +7,8 @@ use { solana_program_error::ProgramError, solana_pubkey::Pubkey, spl_discriminator::SplDiscriminate, - spl_pod::{ - list::{self, ListView}, - primitives::PodU32, - }, + spl_list_view::{ListView, ListViewReadOnly}, + spl_pod::primitives::PodU32, spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut}, std::future::Future, }; @@ -191,7 +189,7 @@ impl ExtraAccountMetaList { /// `TlvStateBorrowed`. I hope there's a better way to do this! pub fn unpack_with_tlv_state<'a, T: SplDiscriminate>( tlv_state: &'a TlvStateBorrowed, - ) -> Result, ProgramError> { + ) -> Result, ProgramError> { let bytes = tlv_state.get_first_bytes::()?; ListView::::unpack(bytes) }