From 307d10ea445a385f3e0ce23152c0ac47cd88574c Mon Sep 17 00:00:00 2001 From: Joe C Date: Thu, 22 Jun 2023 12:29:19 -0400 Subject: [PATCH] `spl discriminator` crate (#4556) * spl discriminator * fixed tlv impls for spec * resolved some comments (naming conventions) * remove TLV discrims * new_with_hash_input fn * HasDiscriminator * added len function * SplDiscriminates * fix dependency issues * merge fix lockfile * bugfix on fat-finger in tlv libs * update ArrayDiscriminator * switch to SplDiscriminate * drop len() --- Cargo.lock | 37 ++++- Cargo.toml | 3 +- libraries/discriminator/Cargo.toml | 19 +++ libraries/discriminator/README.md | 57 +++++++ libraries/discriminator/derive/Cargo.toml | 16 ++ libraries/discriminator/derive/src/lib.rs | 20 +++ libraries/discriminator/src/discriminator.rs | 68 +++++++++ libraries/discriminator/src/lib.rs | 69 +++++++++ libraries/discriminator/syn/Cargo.toml | 15 ++ libraries/discriminator/syn/src/error.rs | 12 ++ libraries/discriminator/syn/src/lib.rs | 89 +++++++++++ libraries/discriminator/syn/src/parser.rs | 43 ++++++ libraries/tlv-account-resolution/Cargo.toml | 2 + libraries/tlv-account-resolution/README.md | 14 +- libraries/tlv-account-resolution/src/state.rs | 44 +++--- libraries/type-length-value/Cargo.toml | 1 + libraries/type-length-value/README.md | 16 +- .../type-length-value/src/discriminator.rs | 63 -------- libraries/type-length-value/src/lib.rs | 1 - libraries/type-length-value/src/state.rs | 141 +++++++++--------- token-metadata/interface/Cargo.toml | 1 + token-metadata/interface/src/instruction.rs | 93 ++++-------- token-metadata/interface/src/state.rs | 13 +- token/transfer-hook-interface/Cargo.toml | 1 + .../src/instruction.rs | 39 ++--- 25 files changed, 616 insertions(+), 261 deletions(-) create mode 100644 libraries/discriminator/Cargo.toml create mode 100644 libraries/discriminator/README.md create mode 100644 libraries/discriminator/derive/Cargo.toml create mode 100644 libraries/discriminator/derive/src/lib.rs create mode 100644 libraries/discriminator/src/discriminator.rs create mode 100644 libraries/discriminator/src/lib.rs create mode 100644 libraries/discriminator/syn/Cargo.toml create mode 100644 libraries/discriminator/syn/src/error.rs create mode 100644 libraries/discriminator/syn/src/lib.rs create mode 100644 libraries/discriminator/syn/src/parser.rs delete mode 100644 libraries/type-length-value/src/discriminator.rs diff --git a/Cargo.lock b/Cargo.lock index b04976ee456..f39bfc8f502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5886,6 +5886,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "spl-discriminator" +version = "0.1.0" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.0" +dependencies = [ + "quote 1.0.26", + "spl-discriminator-syn", + "syn 2.0.15", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.56", + "quote 1.0.26", + "solana-program", + "syn 2.0.15", + "thiserror", +] + [[package]] name = "spl-example-cross-program-invocation" version = "1.0.0" @@ -6292,6 +6321,7 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", + "spl-discriminator", "spl-type-length-value", "thiserror", ] @@ -6482,6 +6512,7 @@ version = "0.1.0" dependencies = [ "borsh", "solana-program", + "spl-discriminator", "spl-program-error", "spl-type-length-value", ] @@ -6581,6 +6612,7 @@ dependencies = [ "num-traits", "num_enum 0.6.1", "solana-program", + "spl-discriminator", "spl-tlv-account-resolution", "spl-type-length-value", "thiserror", @@ -6597,6 +6629,7 @@ dependencies = [ "num-traits", "num_enum 0.6.1", "solana-program", + "spl-discriminator", "thiserror", ] @@ -7457,9 +7490,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" diff --git a/Cargo.toml b/Cargo.toml index 6d2ea917f83..164a5a60732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,9 @@ members = [ "governance/tools", "governance/chat/program", "instruction-padding/program", - "libraries/math", + "libraries/discriminator", "libraries/concurrent-merkle-tree", + "libraries/math", "libraries/merkle-tree-reference", "libraries/program-error", "libraries/tlv-account-resolution", diff --git a/libraries/discriminator/Cargo.toml b/libraries/discriminator/Cargo.toml new file mode 100644 index 00000000000..ab06894aba3 --- /dev/null +++ b/libraries/discriminator/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "spl-discriminator" +version = "0.1.0" +description = "Solana Program Library 8-Byte Discriminator Management" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +bytemuck = { version = "1.13.1", features = ["derive"] } +solana-program = "1.14.12" +spl-discriminator-derive = { version = "0.1.0", path = "./derive" } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/libraries/discriminator/README.md b/libraries/discriminator/README.md new file mode 100644 index 00000000000..104783b6b08 --- /dev/null +++ b/libraries/discriminator/README.md @@ -0,0 +1,57 @@ +# SPL Discriminator + +This library allows for easy management of 8-byte discriminators. + +### The `ArrayDiscriminator` Struct + +With this crate, you can leverage the `ArrayDiscriminator` type to manage an 8-byte discriminator for generic purposes. + +```rust +let my_discriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]); +``` + +The `new(..)` function is also a **constant function**, so you can use `ArrayDiscriminator` in constants as well. + +```rust +const MY_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]); +``` + +The `ArrayDiscriminator` struct also offers another constant function `as_slice(&self)`, so you can use `as_slice()` in constants as well. + +```rust +const MY_DISCRIMINATOR_SLICE: &[u8] = MY_DISCRIMINATOR.as_slice(); +``` + +### The `SplDiscriminate` Trait + +A trait, `SplDiscriminate` is also available, which will give you the `ArrayDiscriminator` constant type and also a slice representation of the discriminator. This can be particularly handy with match statements. + +```rust +/// A trait for managing 8-byte discriminators in a slab of bytes +pub trait SplDiscriminate { + /// The 8-byte discriminator as a `[u8; 8]` + const SPL_DISCRIMINATOR: ArrayDiscriminator; + /// The 8-byte discriminator as a slice (`&[u8]`) + const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice(); +} +``` + +### The `SplDiscriminate` Derive Macro + +The `SplDiscriminate` derive macro is a particularly useful tool for those who wish to derive their 8-byte discriminator from a particular string literal. Typically, you would have to run a hash function against the string literal, then copy the first 8 bytes, and then hard-code those bytes into a statement like the one above. + +Instead, you can simply annotate a struct or enum with `SplDiscriminate` and provide a **namespace** via the `discriminator_namespace` attribute, and the macro will automatically derive the 8-byte discriminator for you! + +```rust +#[derive(SplDiscriminate)] // Implements `SplDiscriminate` for your struct/enum using your declared string literal namespace +#[discriminator_namespace("some_discriminator_namespace")] +pub struct MyInstruction1 { + arg1: String, + arg2: u8, +} + +let my_discriminator: ArrayDiscriminator = MyInstruction1::SPL_DISCRIMINATOR; +let my_discriminator_slice: &[u8] = MyInstruction1::SPL_DISCRIMINATOR_SLICE; +``` + +Note: the 8-byte discriminator derived using the macro is always the **first 8 bytes** of the resulting hashed bytes. diff --git a/libraries/discriminator/derive/Cargo.toml b/libraries/discriminator/derive/Cargo.toml new file mode 100644 index 00000000000..e506ccc70af --- /dev/null +++ b/libraries/discriminator/derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "spl-discriminator-derive" +version = "0.1.0" +description = "Derive macro library for the `spl-discriminator` library" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +spl-discriminator-syn = { version = "0.1.0", path = "../syn" } +syn = { version = "2.0", features = ["full"] } \ No newline at end of file diff --git a/libraries/discriminator/derive/src/lib.rs b/libraries/discriminator/derive/src/lib.rs new file mode 100644 index 00000000000..85580a25dda --- /dev/null +++ b/libraries/discriminator/derive/src/lib.rs @@ -0,0 +1,20 @@ +//! Derive macro library for the `spl-discriminator` library + +#![deny(missing_docs)] +#![cfg_attr(not(test), forbid(unsafe_code))] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::ToTokens; +use spl_discriminator_syn::SplDiscriminateBuilder; +use syn::parse_macro_input; + +/// Derive macro library to implement the `SplDiscriminate` trait +/// on an enum or struct +#[proc_macro_derive(SplDiscriminate, attributes(discriminator_hash_input))] +pub fn spl_discriminator(input: TokenStream) -> TokenStream { + parse_macro_input!(input as SplDiscriminateBuilder) + .to_token_stream() + .into() +} diff --git a/libraries/discriminator/src/discriminator.rs b/libraries/discriminator/src/discriminator.rs new file mode 100644 index 00000000000..448fb2c52b9 --- /dev/null +++ b/libraries/discriminator/src/discriminator.rs @@ -0,0 +1,68 @@ +//! The traits and types used to create a discriminator for a type + +use { + bytemuck::{Pod, Zeroable}, + solana_program::{hash, program_error::ProgramError}, +}; + +/// A trait for managing 8-byte discriminators in a slab of bytes +pub trait SplDiscriminate { + /// The 8-byte discriminator as a `[u8; 8]` + const SPL_DISCRIMINATOR: ArrayDiscriminator; + /// The 8-byte discriminator as a slice (`&[u8]`) + const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice(); +} + +/// Array Discriminator type +#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] +#[repr(transparent)] +pub struct ArrayDiscriminator([u8; ArrayDiscriminator::LENGTH]); +impl ArrayDiscriminator { + /// Size for discriminator in account data + pub const LENGTH: usize = 8; + /// Uninitialized variant of a discriminator + pub const UNINITIALIZED: Self = Self::new([0; Self::LENGTH]); + /// Creates a discriminator from an array + pub const fn new(value: [u8; Self::LENGTH]) -> Self { + Self(value) + } + /// Get the array as a const slice + pub const fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + /// Creates a new `ArrayDiscriminator` from some hash input string literal + pub fn new_with_hash_input(hash_input: &str) -> Self { + let hash_bytes = hash::hashv(&[hash_input.as_bytes()]).to_bytes(); + let mut discriminator_bytes = [0u8; 8]; + discriminator_bytes.copy_from_slice(&hash_bytes[..8]); + Self(discriminator_bytes) + } +} +impl AsRef<[u8]> for ArrayDiscriminator { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} +impl AsRef<[u8; ArrayDiscriminator::LENGTH]> for ArrayDiscriminator { + fn as_ref(&self) -> &[u8; ArrayDiscriminator::LENGTH] { + &self.0 + } +} +impl From for ArrayDiscriminator { + fn from(from: u64) -> Self { + Self(from.to_le_bytes()) + } +} +impl From<[u8; Self::LENGTH]> for ArrayDiscriminator { + fn from(from: [u8; Self::LENGTH]) -> Self { + Self(from) + } +} +impl TryFrom<&[u8]> for ArrayDiscriminator { + type Error = ProgramError; + fn try_from(a: &[u8]) -> Result { + <[u8; Self::LENGTH]>::try_from(a) + .map(Self::from) + .map_err(|_| ProgramError::InvalidAccountData) + } +} diff --git a/libraries/discriminator/src/lib.rs b/libraries/discriminator/src/lib.rs new file mode 100644 index 00000000000..f43741090c3 --- /dev/null +++ b/libraries/discriminator/src/lib.rs @@ -0,0 +1,69 @@ +//! Crate defining a discriminator type, which creates a set of bytes +//! meant to be unique for instructions or struct types + +#![deny(missing_docs)] +#![cfg_attr(not(test), forbid(unsafe_code))] + +extern crate self as spl_discriminator; + +/// Exports the discriminator module +pub mod discriminator; + +// Export for downstream +pub use { + discriminator::{ArrayDiscriminator, SplDiscriminate}, + spl_discriminator_derive::SplDiscriminate, +}; + +#[cfg(test)] +mod tests { + use {super::*, crate::discriminator::ArrayDiscriminator}; + + #[allow(dead_code)] + #[derive(SplDiscriminate)] + #[discriminator_hash_input("some_discriminator_hash_input")] + pub struct MyInstruction1 { + arg1: String, + arg2: u8, + } + + #[allow(dead_code)] + #[derive(SplDiscriminate)] + #[discriminator_hash_input("yet_another_discriminator_hash_input")] + pub struct MyInstruction2 { + arg1: u64, + } + + #[allow(dead_code)] + #[derive(SplDiscriminate)] + #[discriminator_hash_input("global:my_instruction_3")] + pub enum MyInstruction3 { + One, + Two, + Three, + } + + fn assert_discriminator( + hash_input: &str, + ) { + let discriminator = build_discriminator(hash_input); + assert_eq!(T::SPL_DISCRIMINATOR, discriminator); + assert_eq!(T::SPL_DISCRIMINATOR_SLICE, discriminator.as_slice()); + } + + fn build_discriminator(hash_input: &str) -> ArrayDiscriminator { + let preimage = solana_program::hash::hashv(&[hash_input.as_bytes()]); + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&preimage.to_bytes()[..8]); + ArrayDiscriminator::new(bytes) + } + + #[test] + fn test_discrminators() { + assert_discriminator::("some_discriminator_hash_input"); + assert_discriminator::("yet_another_discriminator_hash_input"); + assert_discriminator::("global:my_instruction_3"); + let runtime_discrim = ArrayDiscriminator::new_with_hash_input("my_new_hash_input"); + assert_eq!(runtime_discrim, build_discriminator("my_new_hash_input"),); + } +} diff --git a/libraries/discriminator/syn/Cargo.toml b/libraries/discriminator/syn/Cargo.toml new file mode 100644 index 00000000000..b353fe2fa00 --- /dev/null +++ b/libraries/discriminator/syn/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "spl-discriminator-syn" +version = "0.1.0" +description = "Token parsing and generating library for the `spl-discriminator` library" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +solana-program = "1.14.12" +syn = { version = "2.0", features = ["full"] } +thiserror = "1.0" \ No newline at end of file diff --git a/libraries/discriminator/syn/src/error.rs b/libraries/discriminator/syn/src/error.rs new file mode 100644 index 00000000000..5dd4c30d3c2 --- /dev/null +++ b/libraries/discriminator/syn/src/error.rs @@ -0,0 +1,12 @@ +//! Error types for the `hash_input` parser + +/// Error types for the `hash_input` parser +#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] +pub enum SplDiscriminateError { + /// Discriminator hash_input attribute not provided + #[error("Discriminator `hash_input` attribute not provided")] + HashInputAttributeNotProvided, + /// Error parsing discriminator hash_input attribute + #[error("Error parsing discriminator `hash_input` attribute")] + HashInputAttributeParseError, +} diff --git a/libraries/discriminator/syn/src/lib.rs b/libraries/discriminator/syn/src/lib.rs new file mode 100644 index 00000000000..d51d005a1b2 --- /dev/null +++ b/libraries/discriminator/syn/src/lib.rs @@ -0,0 +1,89 @@ +//! Token parsing and generating library for the `spl-discriminator` library + +#![deny(missing_docs)] +#![cfg_attr(not(test), forbid(unsafe_code))] + +mod error; +pub mod parser; + +use { + crate::{error::SplDiscriminateError, parser::parse_hash_input}, + proc_macro2::{Span, TokenStream}, + quote::{quote, ToTokens}, + solana_program::hash, + syn::{parse::Parse, Ident, Item, ItemEnum, ItemStruct, LitByteStr}, +}; + +/// "Builder" struct to implement the `SplDiscriminate` trait +/// on an enum or struct +#[derive(Debug)] +pub struct SplDiscriminateBuilder { + /// The struct/enum identifier + pub ident: Ident, + /// The TLV hash_input + pub hash_input: String, +} + +impl TryFrom for SplDiscriminateBuilder { + type Error = SplDiscriminateError; + + fn try_from(item_enum: ItemEnum) -> Result { + let ident = item_enum.ident; + let hash_input = parse_hash_input(&item_enum.attrs)?; + Ok(Self { ident, hash_input }) + } +} + +impl TryFrom for SplDiscriminateBuilder { + type Error = SplDiscriminateError; + + fn try_from(item_struct: ItemStruct) -> Result { + let ident = item_struct.ident; + let hash_input = parse_hash_input(&item_struct.attrs)?; + Ok(Self { ident, hash_input }) + } +} + +impl Parse for SplDiscriminateBuilder { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let item = Item::parse(input)?; + match item { + Item::Enum(item_enum) => item_enum.try_into(), + Item::Struct(item_struct) => item_struct.try_into(), + _ => { + return Err(syn::Error::new( + Span::call_site(), + "Only enums and structs are supported", + )) + } + } + .map_err(|e| syn::Error::new(input.span(), format!("Failed to parse item: {}", e))) + } +} + +impl ToTokens for SplDiscriminateBuilder { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend::(self.into()); + } +} + +impl From<&SplDiscriminateBuilder> for TokenStream { + fn from(builder: &SplDiscriminateBuilder) -> Self { + let ident = &builder.ident; + let bytes = get_discriminator_bytes(&builder.hash_input); + quote! { + impl spl_discriminator::discriminator::SplDiscriminate for #ident { + const SPL_DISCRIMINATOR: spl_discriminator::discriminator::ArrayDiscriminator + = spl_discriminator::discriminator::ArrayDiscriminator::new(*#bytes); + } + } + } +} + +/// Returns the bytes for the TLV hash_input discriminator +fn get_discriminator_bytes(hash_input: &str) -> LitByteStr { + LitByteStr::new( + &hash::hashv(&[hash_input.as_bytes()]).to_bytes()[..8], + Span::call_site(), + ) +} diff --git a/libraries/discriminator/syn/src/parser.rs b/libraries/discriminator/syn/src/parser.rs new file mode 100644 index 00000000000..1d70c3ab304 --- /dev/null +++ b/libraries/discriminator/syn/src/parser.rs @@ -0,0 +1,43 @@ +//! Parser for the `syn` crate to parse the +//! `#[discriminator_hash_input("...")]` attribute + +use { + crate::error::SplDiscriminateError, + syn::{ + parse::{Parse, ParseStream}, + token::Comma, + Attribute, LitStr, + }, +}; + +/// Struct used for `syn` parsing of the hash_input attribute +/// #[discriminator_hash_input("...")] +struct HashInputValueParser { + value: LitStr, + _comma: Option, +} + +impl Parse for HashInputValueParser { + fn parse(input: ParseStream) -> syn::Result { + let value: LitStr = input.parse()?; + let _comma: Option = input.parse().unwrap_or(None); + Ok(HashInputValueParser { value, _comma }) + } +} + +/// Parses the hash_input from the `#[discriminator_hash_input("...")]` +/// attribute +pub fn parse_hash_input(attrs: &[Attribute]) -> Result { + match attrs + .iter() + .find(|a| a.path().is_ident("discriminator_hash_input")) + { + Some(attr) => { + let parsed_args = attr + .parse_args::() + .map_err(|_| SplDiscriminateError::HashInputAttributeParseError)?; + Ok(parsed_args.value.value()) + } + None => Err(SplDiscriminateError::HashInputAttributeNotProvided), + } +} diff --git a/libraries/tlv-account-resolution/Cargo.toml b/libraries/tlv-account-resolution/Cargo.toml index 9a82d79b9d6..05cd9d1c684 100644 --- a/libraries/tlv-account-resolution/Cargo.toml +++ b/libraries/tlv-account-resolution/Cargo.toml @@ -16,12 +16,14 @@ num-derive = "0.3" num-traits = "0.2" num_enum = "0.6.1" solana-program = "1.14.12" +spl-discriminator = { version = "0.1", path = "../discriminator" } spl-type-length-value = { version = "0.1", path = "../type-length-value" } thiserror = "1.0" [dev-dependencies] solana-program-test = "1.14.12" solana-sdk = "1.14.12" +spl-discriminator = { version = "0.1", path = "../discriminator" } [lib] crate-type = ["cdylib", "lib"] diff --git a/libraries/tlv-account-resolution/README.md b/libraries/tlv-account-resolution/README.md index 63cf368d4f6..d1fbc17a04a 100644 --- a/libraries/tlv-account-resolution/README.md +++ b/libraries/tlv-account-resolution/README.md @@ -11,14 +11,14 @@ into a TLV entry in an account, you can do the following: ```rust use { solana_program::{account_info::AccountInfo, instruction::{AccountMeta, Instruction}, pubkey::Pubkey}, - spl_type_length_value::discriminator::{Discriminator, TlvDiscriminator}, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, spl_tlv_account_resolution::state::ExtraAccountMetas, }; struct MyInstruction; -impl TlvDiscriminator for MyInstruction { +impl SplDiscriminate for MyInstruction { // For ease of use, give it the same discriminator as its instruction definition - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); + const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); } // Actually put it in the additional required account keys and signer / writable @@ -105,12 +105,12 @@ This library uses `spl-type-length-value` to read and write required instruction accounts from account data. Interface instructions must have an 8-byte discriminator, so that the exposed -`ExtraAccountMetas` type can use the instruction discriminator as a `TlvDiscriminator`. +`ExtraAccountMetas` type can use the instruction discriminator as a `ArrayDiscriminator`. -This can be confusing. Typically, a type implements `TlvDiscriminator`, so that +This can be confusing. Typically, a type implements `SplDiscriminate`, so that the type can be written into TLV data. In this case, `ExtraAccountMetas` is -generic over `TlvDiscriminator`, meaning that a program can write many different instances of -`ExtraAccountMetas` into one account, using different `TlvDiscriminator`s. +generic over `SplDiscriminate`, meaning that a program can write many different instances of +`ExtraAccountMetas` into one account, using different `ArrayDiscriminator`s. Also, it's reusing an instruction discriminator as a TLV discriminator. For example, if the `transfer` instruction has a discriminator of `[1, 2, 3, 4, 5, 6, 7, 8]`, diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 6b77bf29adb..17ed55771a4 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -10,17 +10,15 @@ use { instruction::{AccountMeta, Instruction}, program_error::ProgramError, }, - spl_type_length_value::{ - discriminator::TlvDiscriminator, - state::{TlvState, TlvStateBorrowed, TlvStateMut}, - }, + spl_discriminator::SplDiscriminate, + spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut}, }; /// Stateless helper for storing additional accounts required for an instruction. /// -/// This struct works with any `TlvDiscriminator`, and stores the extra accounts -/// needed for that specific instruction, using the given `Discriminator` as the -/// type-length-value `Discriminator`, and then storing all of the given +/// This struct works with any `SplDiscriminate`, and stores the extra accounts +/// needed for that specific instruction, using the given `ArrayDiscriminator` as the +/// type-length-value `ArrayDiscriminator`, and then storing all of the given /// `AccountMeta`s as a zero-copy slice. /// /// Sample usage: @@ -31,14 +29,14 @@ use { /// account_info::AccountInfo, instruction::{AccountMeta, Instruction}, /// pubkey::Pubkey /// }, -/// spl_type_length_value::discriminator::{Discriminator, TlvDiscriminator}, +/// spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, /// spl_tlv_account_resolution::state::ExtraAccountMetas, /// }; /// /// struct MyInstruction; -/// impl TlvDiscriminator for MyInstruction { +/// impl SplDiscriminate for MyInstruction { /// // Give it a unique discriminator, can also be generated using a hash function -/// const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); +/// const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); /// } /// /// // actually put it in the additional required account keys and signer / writable @@ -76,7 +74,7 @@ pub struct ExtraAccountMetas; impl ExtraAccountMetas { /// Initialize pod slice data for the given instruction and any type /// convertible to account metas - pub fn init<'a, T: TlvDiscriminator, M>( + pub fn init<'a, T: SplDiscriminate, M>( data: &mut [u8], convertible_account_metas: &'a [M], ) -> Result<(), ProgramError> @@ -95,7 +93,7 @@ impl ExtraAccountMetas { /// Initialize a TLV entry for the given discriminator, populating the data /// with the given account infos - pub fn init_with_account_infos( + pub fn init_with_account_infos( data: &mut [u8], account_infos: &[AccountInfo<'_>], ) -> Result<(), ProgramError> { @@ -107,7 +105,7 @@ impl ExtraAccountMetas { /// Due to lifetime annoyances, this function can't just take in the bytes, /// since then we would be returning a reference to a locally created /// `TlvStateBorrowed`. I hope there's a better way to do this! - pub fn unpack_with_tlv_state<'a, T: TlvDiscriminator>( + pub fn unpack_with_tlv_state<'a, T: SplDiscriminate>( tlv_state: &'a TlvStateBorrowed, ) -> Result, ProgramError> { let bytes = tlv_state.get_bytes::()?; @@ -116,7 +114,7 @@ impl ExtraAccountMetas { /// Initialize a TLV entry for the given discriminator, populating the data /// with the given account metas - pub fn init_with_account_metas( + pub fn init_with_account_metas( data: &mut [u8], account_metas: &[AccountMeta], ) -> Result<(), ProgramError> { @@ -160,7 +158,7 @@ impl ExtraAccountMetas { } /// Add the additional account metas to an existing instruction - pub fn add_to_vec( + pub fn add_to_vec( account_metas: &mut Vec, data: &[u8], ) -> Result<(), ProgramError> { @@ -180,7 +178,7 @@ impl ExtraAccountMetas { } /// Add the additional account metas to an existing instruction - pub fn add_to_instruction( + pub fn add_to_instruction( instruction: &mut Instruction, data: &[u8], ) -> Result<(), ProgramError> { @@ -193,7 +191,7 @@ impl ExtraAccountMetas { /// If an added account already exists in the instruction with lower /// privileges, match it to the existing account. This prevents a lower /// program from gaining unexpected privileges. - pub fn add_to_cpi_instruction<'a, T: TlvDiscriminator>( + pub fn add_to_cpi_instruction<'a, T: SplDiscriminate>( cpi_instruction: &mut Instruction, cpi_account_infos: &mut Vec>, data: &[u8], @@ -228,17 +226,19 @@ mod tests { use { super::*, solana_program::{clock::Epoch, instruction::AccountMeta, pubkey::Pubkey}, - spl_type_length_value::discriminator::Discriminator, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, }; pub struct TestInstruction; - impl TlvDiscriminator for TestInstruction { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); + impl SplDiscriminate for TestInstruction { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); } pub struct TestOtherInstruction; - impl TlvDiscriminator for TestOtherInstruction { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([2; Discriminator::LENGTH]); + impl SplDiscriminate for TestOtherInstruction { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]); } #[test] diff --git a/libraries/type-length-value/Cargo.toml b/libraries/type-length-value/Cargo.toml index 29397f6cb7f..3af4805f5b4 100644 --- a/libraries/type-length-value/Cargo.toml +++ b/libraries/type-length-value/Cargo.toml @@ -19,6 +19,7 @@ num-derive = "0.3" num-traits = "0.2" num_enum = "0.6.1" solana-program = "1.14.12" +spl-discriminator = { version = "0.1.0", path = "../discriminator" } thiserror = "1.0" [lib] diff --git a/libraries/type-length-value/README.md b/libraries/type-length-value/README.md index bc4fb431f6d..b5b2c4168f4 100644 --- a/libraries/type-length-value/README.md +++ b/libraries/type-length-value/README.md @@ -10,8 +10,8 @@ This simple examples defines a zero-copy type with its discriminator. use { borsh::{BorshSerialize, BorshDeserialize}, bytemuck::{Pod, Zeroable}, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate} spl_type_length_value::{ - discriminator::{Discriminator, TlvDiscriminator}, state::{TlvState, TlvStateBorrowed, TlvStateMut} }, }; @@ -21,9 +21,9 @@ use { struct MyPodValue { data: [u8; 32], } -impl TlvDiscriminator for MyPodValue { +impl SpleDiscriminates for MyPodValue { // Give it a unique discriminator, can also be generated using a hash function - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); + const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); } #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] @@ -38,9 +38,9 @@ impl Default for MyOtherPodValue { } } } -impl TlvDiscriminator for MyOtherPodValue { +impl SplDiscriminate for MyOtherPodValue { // Some other unique discriminator - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([2; Discriminator::LENGTH]); + const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]); } // Account will have two sets of `get_base_len()` (8-byte discriminator and 4-byte length), @@ -97,7 +97,7 @@ scheme facilitates this exact case. This library allows for holding multiple disparate types within the same account by encoding the type, then length, then value. -The type is an 8-byte `Discriminator`, which can be set to anything. +The type is an 8-byte `ArrayDiscriminator`, which can be set to anything. The length is a little-endian `u32`. @@ -123,8 +123,8 @@ use { struct MyBorsh { data: String, // variable length type } -impl TlvDiscriminator for MyBorsh { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([5; Discriminator::LENGTH]); +impl SplDiscriminate for MyBorsh { + const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([5; ArrayDiscriminator::LENGTH]); } let initial_data = "This is a pretty cool test!"; // Allocate exactly the right size for the string, can go bigger if desired diff --git a/libraries/type-length-value/src/discriminator.rs b/libraries/type-length-value/src/discriminator.rs deleted file mode 100644 index c04c6b8536a..00000000000 --- a/libraries/type-length-value/src/discriminator.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Discriminator for differentiating account types, the "Type" in the -//! Type-Length-Value structure. Since the word "type" is reserved in Rust, -//! we use the term "Discriminator" and "Type" interchangeably. - -use { - bytemuck::{Pod, Zeroable}, - solana_program::program_error::ProgramError, -}; - -/// Trait to be implemented by all value types in the TLV structure, specifying -/// just the discriminator -pub trait TlvDiscriminator { - /// Associated value type discriminator, checked at the start of TLV entries - const TLV_DISCRIMINATOR: Discriminator; -} - -/// Discriminator used as the type in the TLV structure -/// Type in TLV structure -#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] -#[repr(transparent)] -pub struct Discriminator([u8; Discriminator::LENGTH]); -impl Discriminator { - /// Size for discriminator in account data - pub const LENGTH: usize = 8; - /// Uninitialized variant of a discriminator - pub const UNINITIALIZED: Self = Self::new([0; Self::LENGTH]); - /// Creates a discriminator from an array - pub const fn new(value: [u8; Self::LENGTH]) -> Self { - Self(value) - } - /// Get the array as a const slice - pub const fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } -} -impl AsRef<[u8]> for Discriminator { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} -impl AsRef<[u8; Discriminator::LENGTH]> for Discriminator { - fn as_ref(&self) -> &[u8; Discriminator::LENGTH] { - &self.0 - } -} -impl From for Discriminator { - fn from(from: u64) -> Self { - Self(from.to_le_bytes()) - } -} -impl From<[u8; Self::LENGTH]> for Discriminator { - fn from(from: [u8; Self::LENGTH]) -> Self { - Self(from) - } -} -impl TryFrom<&[u8]> for Discriminator { - type Error = ProgramError; - fn try_from(a: &[u8]) -> Result { - <[u8; Self::LENGTH]>::try_from(a) - .map(Self::from) - .map_err(|_| ProgramError::InvalidAccountData) - } -} diff --git a/libraries/type-length-value/src/lib.rs b/libraries/type-length-value/src/lib.rs index 1deef9ec4d9..2e7934baca4 100644 --- a/libraries/type-length-value/src/lib.rs +++ b/libraries/type-length-value/src/lib.rs @@ -5,7 +5,6 @@ #![deny(missing_docs)] #![cfg_attr(not(test), forbid(unsafe_code))] -pub mod discriminator; pub mod error; pub mod length; pub mod pod; diff --git a/libraries/type-length-value/src/state.rs b/libraries/type-length-value/src/state.rs index 8e34baa6fd2..03576bce3cf 100644 --- a/libraries/type-length-value/src/state.rs +++ b/libraries/type-length-value/src/state.rs @@ -2,19 +2,19 @@ use { crate::{ - discriminator::{Discriminator, TlvDiscriminator}, error::TlvError, length::Length, pod::{pod_from_bytes, pod_from_bytes_mut}, }, bytemuck::Pod, solana_program::program_error::ProgramError, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, std::{cmp::Ordering, mem::size_of}, }; /// Get the current TlvIndices from the current spot const fn get_indices_unchecked(type_start: usize) -> TlvIndices { - let length_start = type_start.saturating_add(size_of::()); + let length_start = type_start.saturating_add(size_of::()); let value_start = length_start.saturating_add(size_of::()); TlvIndices { type_start, @@ -33,7 +33,7 @@ struct TlvIndices { } fn get_indices( tlv_data: &[u8], - value_discriminator: Discriminator, + value_discriminator: ArrayDiscriminator, init: bool, ) -> Result { let mut start_index = 0; @@ -42,14 +42,15 @@ fn get_indices( if tlv_data.len() < tlv_indices.value_start { return Err(ProgramError::InvalidAccountData); } - let discriminator = - Discriminator::try_from(&tlv_data[tlv_indices.type_start..tlv_indices.length_start])?; + let discriminator = ArrayDiscriminator::try_from( + &tlv_data[tlv_indices.type_start..tlv_indices.length_start], + )?; if discriminator == value_discriminator { // found an instance of the extension that we're initializing, return! return Ok(tlv_indices); // got to an empty spot, init here, or error if we're searching, since // nothing is written after an Uninitialized spot - } else if discriminator == Discriminator::UNINITIALIZED { + } else if discriminator == ArrayDiscriminator::UNINITIALIZED { if init { return Ok(tlv_indices); } else { @@ -72,7 +73,7 @@ fn get_indices( // better served by some custom iterator, but let's leave that for another day. fn get_discriminators_and_end_index( tlv_data: &[u8], -) -> Result<(Vec, usize), ProgramError> { +) -> Result<(Vec, usize), ProgramError> { let mut discriminators = vec![]; let mut start_index = 0; while start_index < tlv_data.len() { @@ -86,9 +87,10 @@ fn get_discriminators_and_end_index( return Err(ProgramError::InvalidAccountData); } } - let discriminator = - Discriminator::try_from(&tlv_data[tlv_indices.type_start..tlv_indices.length_start])?; - if discriminator == Discriminator::UNINITIALIZED { + let discriminator = ArrayDiscriminator::try_from( + &tlv_data[tlv_indices.type_start..tlv_indices.length_start], + )?; + if discriminator == ArrayDiscriminator::UNINITIALIZED { return Ok((discriminators, tlv_indices.type_start)); } else { if tlv_data.len() < tlv_indices.value_start { @@ -113,12 +115,12 @@ fn get_discriminators_and_end_index( Ok((discriminators, start_index)) } -fn get_bytes(tlv_data: &[u8]) -> Result<&[u8], ProgramError> { +fn get_bytes(tlv_data: &[u8]) -> Result<&[u8], ProgramError> { let TlvIndices { type_start: _, length_start, value_start, - } = get_indices(tlv_data, V::TLV_DISCRIMINATOR, false)?; + } = get_indices(tlv_data, V::SPL_DISCRIMINATOR, false)?; // get_indices has checked that tlv_data is long enough to include these indices let length = pod_from_bytes::(&tlv_data[length_start..value_start])?; let value_end = value_start.saturating_add(usize::try_from(*length)?); @@ -132,7 +134,7 @@ fn get_bytes(tlv_data: &[u8]) -> Result<&[u8], ProgramError /// /// Stores data as any number of type-length-value structures underneath, where: /// -/// * the "type" is a `Discriminator`, 8 bytes +/// * the "type" is an `ArrayDiscriminator`, 8 bytes /// * the "length" is a `Length`, 4 bytes /// * the "value" is a slab of "length" bytes /// @@ -149,26 +151,24 @@ fn get_bytes(tlv_data: &[u8]) -> Result<&[u8], ProgramError /// ``` /// use { /// bytemuck::{Pod, Zeroable}, -/// spl_type_length_value::{ -/// discriminator::{Discriminator, TlvDiscriminator}, -/// state::{TlvState, TlvStateBorrowed, TlvStateMut} -/// }, +/// spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, +/// spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut}, /// }; /// #[repr(C)] /// #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] /// struct MyPodValue { /// data: [u8; 8], /// } -/// impl TlvDiscriminator for MyPodValue { -/// const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); +/// impl SplDiscriminate for MyPodValue { +/// const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); /// } /// #[repr(C)] /// #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] /// struct MyOtherPodValue { /// data: u8, /// } -/// impl TlvDiscriminator for MyOtherPodValue { -/// const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([2; Discriminator::LENGTH]); +/// impl SplDiscriminate for MyOtherPodValue { +/// const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]); /// } /// let buffer = [ /// 1, 1, 1, 1, 1, 1, 1, 1, // first type's discriminator @@ -191,14 +191,14 @@ pub trait TlvState { fn get_data(&self) -> &[u8]; /// Unpack a portion of the TLV data as the desired Pod type - fn get_value(&self) -> Result<&V, ProgramError> { + fn get_value(&self) -> Result<&V, ProgramError> { let data = get_bytes::(self.get_data())?; pod_from_bytes::(data) } /// Unpacks a portion of the TLV data as the desired Borsh type #[cfg(feature = "borsh")] - fn borsh_deserialize( + fn borsh_deserialize( &self, ) -> Result { let data = get_bytes::(self.get_data())?; @@ -206,12 +206,12 @@ pub trait TlvState { } /// Unpack a portion of the TLV data as bytes - fn get_bytes(&self) -> Result<&[u8], ProgramError> { + fn get_bytes(&self) -> Result<&[u8], ProgramError> { get_bytes::(self.get_data()) } /// Iterates through the TLV entries, returning only the types - fn get_discriminators(&self) -> Result, ProgramError> { + fn get_discriminators(&self) -> Result, ProgramError> { get_discriminators_and_end_index(self.get_data()).map(|v| v.0) } @@ -279,18 +279,18 @@ impl<'data> TlvStateMut<'data> { } /// Unpack a portion of the TLV data as the desired type that allows modifying the type - pub fn get_value_mut(&mut self) -> Result<&mut V, ProgramError> { + pub fn get_value_mut(&mut self) -> Result<&mut V, ProgramError> { let data = self.get_bytes_mut::()?; pod_from_bytes_mut::(data) } /// Unpack a portion of the TLV data as mutable bytes - pub fn get_bytes_mut(&mut self) -> Result<&mut [u8], ProgramError> { + pub fn get_bytes_mut(&mut self) -> Result<&mut [u8], ProgramError> { let TlvIndices { type_start: _, length_start, value_start, - } = get_indices(self.data, V::TLV_DISCRIMINATOR, false)?; + } = get_indices(self.data, V::SPL_DISCRIMINATOR, false)?; let length = pod_from_bytes::(&self.data[length_start..value_start])?; let value_end = value_start.saturating_add(usize::try_from(*length)?); @@ -302,7 +302,7 @@ impl<'data> TlvStateMut<'data> { /// Packs the default TLV data into the first open slot in the data buffer. /// If extension is already found in the buffer, it returns an error. - pub fn init_value( + pub fn init_value( &mut self, ) -> Result<&mut V, ProgramError> { let length = size_of::(); @@ -315,7 +315,7 @@ impl<'data> TlvStateMut<'data> { /// Packs a borsh-serializable value into its appropriate data segment. Assumes /// that space has already been allocated for the given type #[cfg(feature = "borsh")] - pub fn borsh_serialize( + pub fn borsh_serialize( &mut self, value: &V, ) -> Result<(), ProgramError> { @@ -323,19 +323,19 @@ impl<'data> TlvStateMut<'data> { borsh::to_writer(&mut data[..], value).map_err(Into::into) } - /// Allocate the given number of bytes for the given TlvDiscriminator - pub fn alloc(&mut self, length: usize) -> Result<&mut [u8], ProgramError> { + /// Allocate the given number of bytes for the given SplDiscriminate + pub fn alloc(&mut self, length: usize) -> Result<&mut [u8], ProgramError> { let TlvIndices { type_start, length_start, value_start, - } = get_indices(self.data, V::TLV_DISCRIMINATOR, true)?; + } = get_indices(self.data, V::SPL_DISCRIMINATOR, true)?; - let discriminator = Discriminator::try_from(&self.data[type_start..length_start])?; - if discriminator == Discriminator::UNINITIALIZED { + let discriminator = ArrayDiscriminator::try_from(&self.data[type_start..length_start])?; + if discriminator == ArrayDiscriminator::UNINITIALIZED { // write type let discriminator_ref = &mut self.data[type_start..length_start]; - discriminator_ref.copy_from_slice(V::TLV_DISCRIMINATOR.as_ref()); + discriminator_ref.copy_from_slice(V::SPL_DISCRIMINATOR.as_ref()); // write length let length_ref = pod_from_bytes_mut::(&mut self.data[length_start..value_start])?; @@ -351,11 +351,11 @@ impl<'data> TlvStateMut<'data> { } } - /// Reallocate the given number of bytes for the given TlvDiscriminator. If the new + /// Reallocate the given number of bytes for the given SplDiscriminate. If the new /// length is smaller, it will compact the rest of the buffer and zero out /// the difference at the end. If it's larger, it will move the rest of /// the buffer data and zero out the new data. - pub fn realloc( + pub fn realloc( &mut self, length: usize, ) -> Result<&mut [u8], ProgramError> { @@ -363,7 +363,7 @@ impl<'data> TlvStateMut<'data> { type_start: _, length_start, value_start, - } = get_indices(self.data, V::TLV_DISCRIMINATOR, false)?; + } = get_indices(self.data, V::SPL_DISCRIMINATOR, false)?; let (_, end_index) = get_discriminators_and_end_index(self.data)?; let data_len = self.data.len(); @@ -447,8 +447,9 @@ mod test { struct TestValue { data: [u8; 32], } - impl TlvDiscriminator for TestValue { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([1; Discriminator::LENGTH]); + impl SplDiscriminate for TestValue { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); } #[repr(C)] @@ -456,15 +457,17 @@ mod test { struct TestSmallValue { data: [u8; 3], } - impl TlvDiscriminator for TestSmallValue { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([2; Discriminator::LENGTH]); + impl SplDiscriminate for TestSmallValue { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]); } #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] struct TestEmptyValue; - impl TlvDiscriminator for TestEmptyValue { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([3; Discriminator::LENGTH]); + impl SplDiscriminate for TestEmptyValue { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([3; ArrayDiscriminator::LENGTH]); } #[repr(C)] @@ -473,8 +476,9 @@ mod test { data: [u8; 5], } const TEST_NON_ZERO_DEFAULT_DATA: [u8; 5] = [4; 5]; - impl TlvDiscriminator for TestNonZeroDefault { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([4; Discriminator::LENGTH]); + impl SplDiscriminate for TestNonZeroDefault { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([4; ArrayDiscriminator::LENGTH]); } impl Default for TestNonZeroDefault { fn default() -> Self { @@ -531,7 +535,7 @@ mod test { // tweak the length, too big let mut buffer = TEST_BUFFER.to_vec(); - buffer[Discriminator::LENGTH] += 10; + buffer[ArrayDiscriminator::LENGTH] += 10; assert_eq!( TlvStateMut::unpack(&mut buffer), Err(ProgramError::InvalidAccountData) @@ -539,7 +543,7 @@ mod test { // tweak the length, too small let mut buffer = TEST_BIG_BUFFER.to_vec(); - buffer[Discriminator::LENGTH] -= 1; + buffer[ArrayDiscriminator::LENGTH] -= 1; let state = TlvStateMut::unpack(&mut buffer).unwrap(); assert_eq!( state.get_value::(), @@ -564,7 +568,7 @@ mod test { // correct due to the good discriminator length and zero length assert_eq!( get_discriminators_and_end_index(&[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(), - (vec![Discriminator::try_from(1).unwrap()], 12) + (vec![ArrayDiscriminator::try_from(1).unwrap()], 12) ); // correct since it's just uninitialized data assert_eq!( @@ -587,7 +591,7 @@ mod test { value.data = data; assert_eq!( &state.get_discriminators().unwrap(), - &[TestValue::TLV_DISCRIMINATOR], + &[TestValue::SPL_DISCRIMINATOR], ); assert_eq!(&state.get_value::().unwrap().data, &data,); @@ -599,10 +603,10 @@ mod test { // check raw buffer let mut expect = vec![]; - expect.extend_from_slice(TestValue::TLV_DISCRIMINATOR.as_ref()); + expect.extend_from_slice(TestValue::SPL_DISCRIMINATOR.as_ref()); expect.extend_from_slice(&u32::try_from(size_of::()).unwrap().to_le_bytes()); expect.extend_from_slice(&data); - expect.extend_from_slice(&[0; size_of::()]); + expect.extend_from_slice(&[0; size_of::()]); expect.extend_from_slice(&[0; size_of::()]); expect.extend_from_slice(&[0; size_of::()]); assert_eq!(expect, buffer); @@ -623,10 +627,10 @@ mod test { // check raw buffer let mut expect = vec![]; - expect.extend_from_slice(TestValue::TLV_DISCRIMINATOR.as_ref()); + expect.extend_from_slice(TestValue::SPL_DISCRIMINATOR.as_ref()); expect.extend_from_slice(&u32::try_from(size_of::()).unwrap().to_le_bytes()); expect.extend_from_slice(&new_data); - expect.extend_from_slice(&[0; size_of::()]); + expect.extend_from_slice(&[0; size_of::()]); expect.extend_from_slice(&[0; size_of::()]); expect.extend_from_slice(&[0; size_of::()]); assert_eq!(expect, buffer); @@ -640,17 +644,17 @@ mod test { assert_eq!( &state.get_discriminators().unwrap(), &[ - TestValue::TLV_DISCRIMINATOR, - TestSmallValue::TLV_DISCRIMINATOR + TestValue::SPL_DISCRIMINATOR, + TestSmallValue::SPL_DISCRIMINATOR ] ); // check raw buffer let mut expect = vec![]; - expect.extend_from_slice(TestValue::TLV_DISCRIMINATOR.as_ref()); + expect.extend_from_slice(TestValue::SPL_DISCRIMINATOR.as_ref()); expect.extend_from_slice(&u32::try_from(size_of::()).unwrap().to_le_bytes()); expect.extend_from_slice(&new_data); - expect.extend_from_slice(TestSmallValue::TLV_DISCRIMINATOR.as_ref()); + expect.extend_from_slice(TestSmallValue::SPL_DISCRIMINATOR.as_ref()); expect.extend_from_slice( &u32::try_from(size_of::()) .unwrap() @@ -687,8 +691,8 @@ mod test { assert_eq!( &state.get_discriminators().unwrap(), &[ - TestValue::TLV_DISCRIMINATOR, - TestSmallValue::TLV_DISCRIMINATOR, + TestValue::SPL_DISCRIMINATOR, + TestSmallValue::SPL_DISCRIMINATOR, ] ); @@ -704,8 +708,8 @@ mod test { assert_eq!( &state.get_discriminators().unwrap(), &[ - TestSmallValue::TLV_DISCRIMINATOR, - TestValue::TLV_DISCRIMINATOR, + TestSmallValue::SPL_DISCRIMINATOR, + TestValue::SPL_DISCRIMINATOR, ] ); @@ -743,9 +747,9 @@ mod test { assert_eq!(err, ProgramError::InvalidAccountData); // hack the buffer to look like it was initialized, still fails - let discriminator_ref = &mut state.data[0..Discriminator::LENGTH]; - discriminator_ref.copy_from_slice(TestValue::TLV_DISCRIMINATOR.as_ref()); - state.data[Discriminator::LENGTH] = 32; + let discriminator_ref = &mut state.data[0..ArrayDiscriminator::LENGTH]; + discriminator_ref.copy_from_slice(TestValue::SPL_DISCRIMINATOR.as_ref()); + state.data[ArrayDiscriminator::LENGTH] = 32; let err = state.get_value::().unwrap_err(); assert_eq!(err, ProgramError::InvalidAccountData); assert_eq!( @@ -853,8 +857,9 @@ mod borsh_test { struct TestInnerBorsh { data: String, } - impl TlvDiscriminator for TestBorsh { - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new([5; Discriminator::LENGTH]); + impl SplDiscriminate for TestBorsh { + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([5; ArrayDiscriminator::LENGTH]); } #[test] fn borsh_value() { diff --git a/token-metadata/interface/Cargo.toml b/token-metadata/interface/Cargo.toml index 23e5e12bf15..49c61d68639 100644 --- a/token-metadata/interface/Cargo.toml +++ b/token-metadata/interface/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [dependencies] borsh = "0.9" solana-program = "1.14.12" +spl-discriminator = { version = "0.1.0" , path = "../../libraries/discriminator" } spl-program-error = { version = "0.1.0" , path = "../../libraries/program-error" } spl-type-length-value = { version = "0.1.0" , path = "../../libraries/type-length-value" } diff --git a/token-metadata/interface/src/instruction.rs b/token-metadata/interface/src/instruction.rs index 1aaaccefc18..506a74064f4 100644 --- a/token-metadata/interface/src/instruction.rs +++ b/token-metadata/interface/src/instruction.rs @@ -8,7 +8,7 @@ use { program_error::ProgramError, pubkey::Pubkey, }, - spl_type_length_value::discriminator::{Discriminator, TlvDiscriminator}, + spl_discriminator::{discriminator::ArrayDiscriminator, SplDiscriminate}, }; /// Fields in the metadata account @@ -25,7 +25,8 @@ pub enum Field { } /// Initialization instruction data -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)] +#[discriminator_hash_input("spl_token_metadata_interface:initialize_account")] pub struct Initialize { /// Longer name of the token pub name: String, @@ -34,80 +35,42 @@ pub struct Initialize { /// URI pointing to more metadata (image, video, etc.) pub uri: String, } -impl TlvDiscriminator for Initialize { - /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new(INITIALIZE_DISCRIMINATOR); -} -/// First 8 bytes of `hash::hashv(&["spl_token_metadata_interface:initialize_account"])` -const INITIALIZE_DISCRIMINATOR: [u8; Discriminator::LENGTH] = [210, 225, 30, 162, 88, 184, 77, 141]; -// annoying, but needed to perform a match on the value -const INITIALIZE_DISCRIMINATOR_SLICE: &[u8] = &INITIALIZE_DISCRIMINATOR; /// Update field instruction data -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)] +#[discriminator_hash_input("spl_token_metadata_interface:updating_field")] pub struct UpdateField { /// Field to update in the metadata pub field: Field, /// Value to write for the field pub value: String, } -impl TlvDiscriminator for UpdateField { - /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new(UPDATE_FIELD_DISCRIMINATOR); -} -/// First 8 bytes of `hash::hashv(&["spl_token_metadata_interface:updating_field"])` -const UPDATE_FIELD_DISCRIMINATOR: [u8; Discriminator::LENGTH] = - [221, 233, 49, 45, 181, 202, 220, 200]; -// annoying, but needed to perform a match on the value -const UPDATE_FIELD_DISCRIMINATOR_SLICE: &[u8] = &UPDATE_FIELD_DISCRIMINATOR; /// Remove key instruction data -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)] +#[discriminator_hash_input("spl_token_metadata_interface:remove_key_ix")] pub struct RemoveKey { /// Key to remove in the additional metadata portion pub key: String, } -impl TlvDiscriminator for RemoveKey { - /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new(REMOVE_KEY_DISCRIMINATOR); -} -/// First 8 bytes of `hash::hashv(&["spl_token_metadata_interface:remove_key_ix"])` -const REMOVE_KEY_DISCRIMINATOR: [u8; Discriminator::LENGTH] = [234, 18, 32, 56, 89, 141, 37, 181]; -// annoying, but needed to perform a match on the value -const REMOVE_KEY_DISCRIMINATOR_SLICE: &[u8] = &REMOVE_KEY_DISCRIMINATOR; /// Update authority instruction data -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)] +#[discriminator_hash_input("spl_token_metadata_interface:update_the_authority")] pub struct UpdateAuthority { /// New authority for the token metadata, or unset if `None` pub new_authority: OptionalNonZeroPubkey, } -impl TlvDiscriminator for UpdateAuthority { - /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new(UPDATE_AUTHORITY_DISCRIMINATOR); -} -/// First 8 bytes of `hash::hashv(&["spl_token_metadata_interface:update_the_authority"])` -const UPDATE_AUTHORITY_DISCRIMINATOR: [u8; Discriminator::LENGTH] = - [215, 228, 166, 228, 84, 100, 86, 123]; -// annoying, but needed to perform a match on the value -const UPDATE_AUTHORITY_DISCRIMINATOR_SLICE: &[u8] = &UPDATE_AUTHORITY_DISCRIMINATOR; /// Instruction data for Emit -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)] +#[discriminator_hash_input("spl_token_metadata_interface:emitter")] pub struct Emit { /// Start of range of data to emit pub start: Option, /// End of range of data to emit pub end: Option, } -impl TlvDiscriminator for Emit { - /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = Discriminator::new(EMIT_DISCRIMINATOR); -} -/// First 8 bytes of `hash::hashv(&["spl_token_metadata_interface:emitter"])` -const EMIT_DISCRIMINATOR: [u8; Discriminator::LENGTH] = [250, 166, 180, 250, 13, 12, 184, 70]; -// annoying, but needed to perform a match on the value -const EMIT_DISCRIMINATOR_SLICE: &[u8] = &EMIT_DISCRIMINATOR; /// All instructions that must be implemented in the token-metadata interface #[derive(Clone, Debug, PartialEq)] @@ -195,28 +158,28 @@ pub enum TokenMetadataInstruction { impl TokenMetadataInstruction { /// Unpacks a byte buffer into a [TokenMetadataInstruction](enum.TokenMetadataInstruction.html). pub fn unpack(input: &[u8]) -> Result { - if input.len() < Discriminator::LENGTH { + if input.len() < ArrayDiscriminator::LENGTH { return Err(ProgramError::InvalidInstructionData); } - let (discriminator, rest) = input.split_at(Discriminator::LENGTH); + let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH); Ok(match discriminator { - INITIALIZE_DISCRIMINATOR_SLICE => { + Initialize::SPL_DISCRIMINATOR_SLICE => { let data = Initialize::try_from_slice(rest)?; Self::Initialize(data) } - UPDATE_FIELD_DISCRIMINATOR_SLICE => { + UpdateField::SPL_DISCRIMINATOR_SLICE => { let data = UpdateField::try_from_slice(rest)?; Self::UpdateField(data) } - REMOVE_KEY_DISCRIMINATOR_SLICE => { + RemoveKey::SPL_DISCRIMINATOR_SLICE => { let data = RemoveKey::try_from_slice(rest)?; Self::RemoveKey(data) } - UPDATE_AUTHORITY_DISCRIMINATOR_SLICE => { + UpdateAuthority::SPL_DISCRIMINATOR_SLICE => { let data = UpdateAuthority::try_from_slice(rest)?; Self::UpdateAuthority(data) } - EMIT_DISCRIMINATOR_SLICE => { + Emit::SPL_DISCRIMINATOR_SLICE => { let data = Emit::try_from_slice(rest)?; Self::Emit(data) } @@ -229,23 +192,23 @@ impl TokenMetadataInstruction { let mut buf = vec![]; match self { Self::Initialize(data) => { - buf.extend_from_slice(INITIALIZE_DISCRIMINATOR_SLICE); + buf.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE); buf.append(&mut data.try_to_vec().unwrap()); } Self::UpdateField(data) => { - buf.extend_from_slice(UPDATE_FIELD_DISCRIMINATOR_SLICE); + buf.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE); buf.append(&mut data.try_to_vec().unwrap()); } Self::RemoveKey(data) => { - buf.extend_from_slice(REMOVE_KEY_DISCRIMINATOR_SLICE); + buf.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE); buf.append(&mut data.try_to_vec().unwrap()); } Self::UpdateAuthority(data) => { - buf.extend_from_slice(UPDATE_AUTHORITY_DISCRIMINATOR_SLICE); + buf.extend_from_slice(UpdateAuthority::SPL_DISCRIMINATOR_SLICE); buf.append(&mut data.try_to_vec().unwrap()); } Self::Emit(data) => { - buf.extend_from_slice(EMIT_DISCRIMINATOR_SLICE); + buf.extend_from_slice(Emit::SPL_DISCRIMINATOR_SLICE); buf.append(&mut data.try_to_vec().unwrap()); } }; @@ -378,7 +341,7 @@ mod test { }; let check = TokenMetadataInstruction::Initialize(data.clone()); let preimage = hash::hashv(&[format!("{NAMESPACE}:initialize_account").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; check_pack_unpack(check, discriminator, data); } @@ -392,7 +355,7 @@ mod test { }; let check = TokenMetadataInstruction::UpdateField(data.clone()); let preimage = hash::hashv(&[format!("{NAMESPACE}:updating_field").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; check_pack_unpack(check, discriminator, data); } @@ -403,7 +366,7 @@ mod test { }; let check = TokenMetadataInstruction::RemoveKey(data.clone()); let preimage = hash::hashv(&[format!("{NAMESPACE}:remove_key_ix").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; check_pack_unpack(check, discriminator, data); } @@ -414,7 +377,7 @@ mod test { }; let check = TokenMetadataInstruction::UpdateAuthority(data.clone()); let preimage = hash::hashv(&[format!("{NAMESPACE}:update_the_authority").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; check_pack_unpack(check, discriminator, data); } @@ -426,7 +389,7 @@ mod test { }; let check = TokenMetadataInstruction::Emit(data.clone()); let preimage = hash::hashv(&[format!("{NAMESPACE}:emitter").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; check_pack_unpack(check, discriminator, data); } } diff --git a/token-metadata/interface/src/state.rs b/token-metadata/interface/src/state.rs index 07e039699f6..097cd183296 100644 --- a/token-metadata/interface/src/state.rs +++ b/token-metadata/interface/src/state.rs @@ -1,8 +1,9 @@ //! Token-metadata interface state types + use { borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, solana_program::{program_error::ProgramError, pubkey::Pubkey}, - spl_type_length_value::discriminator::{Discriminator, TlvDiscriminator}, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, std::convert::TryFrom, }; @@ -57,10 +58,10 @@ pub struct TokenMetadata { /// must avoid storing the same key twice. pub additional_metadata: Vec<(String, String)>, } -impl TlvDiscriminator for TokenMetadata { +impl SplDiscriminate for TokenMetadata { /// Please use this discriminator in your program when matching - const TLV_DISCRIMINATOR: Discriminator = - Discriminator::new([112, 132, 90, 90, 11, 88, 157, 87]); + const SPL_DISCRIMINATOR: ArrayDiscriminator = + ArrayDiscriminator::new([112, 132, 90, 90, 11, 88, 157, 87]); } #[cfg(test)] @@ -71,7 +72,7 @@ mod tests { fn discriminator() { let preimage = hash::hashv(&[format!("{NAMESPACE}:token_metadata").as_bytes()]); let discriminator = - Discriminator::try_from(&preimage.as_ref()[..Discriminator::LENGTH]).unwrap(); - assert_eq!(TokenMetadata::TLV_DISCRIMINATOR, discriminator); + ArrayDiscriminator::try_from(&preimage.as_ref()[..ArrayDiscriminator::LENGTH]).unwrap(); + assert_eq!(TokenMetadata::SPL_DISCRIMINATOR, discriminator); } } diff --git a/token/transfer-hook-interface/Cargo.toml b/token/transfer-hook-interface/Cargo.toml index 9280daaf55c..f1667543532 100644 --- a/token/transfer-hook-interface/Cargo.toml +++ b/token/transfer-hook-interface/Cargo.toml @@ -14,6 +14,7 @@ num-derive = "0.3" num-traits = "0.2" num_enum = "0.6.1" solana-program = "1.14.12" +spl-discriminator = { version = "0.1.0" , path = "../../libraries/discriminator" } spl-tlv-account-resolution = { version = "0.1.0" , path = "../../libraries/tlv-account-resolution" } spl-type-length-value = { version = "0.1.0" , path = "../../libraries/type-length-value" } thiserror = "1.0" diff --git a/token/transfer-hook-interface/src/instruction.rs b/token/transfer-hook-interface/src/instruction.rs index 9b08d60c7dc..1e597c2a70d 100644 --- a/token/transfer-hook-interface/src/instruction.rs +++ b/token/transfer-hook-interface/src/instruction.rs @@ -7,7 +7,7 @@ use { pubkey::Pubkey, system_program, }, - spl_type_length_value::discriminator::{Discriminator, TlvDiscriminator}, + spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, std::convert::TryInto, }; @@ -46,26 +46,25 @@ pub enum TransferHookInstruction { /// TLV instruction type only used to define the discriminator. The actual data /// is entirely managed by `ExtraAccountMetas`, and it is the only data contained /// by this type. +#[derive(SplDiscriminate)] +#[discriminator_hash_input("spl-transfer-hook-interface:execute")] pub struct ExecuteInstruction; -impl TlvDiscriminator for ExecuteInstruction { - /// First 8 bytes of `hash::hashv(&["spl-transfer-hook-interface:execute"])` - const TLV_DISCRIMINATOR: Discriminator = - Discriminator::new([105, 37, 101, 197, 75, 251, 102, 26]); -} -/// First 8 bytes of `hash::hashv(&["spl-transfer-hook-interface:initialize-extra-account-metas"])` -const INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR: &[u8] = &[43, 34, 13, 49, 167, 88, 235, 235]; +/// TLV instruction type used to initialize extra account metas +/// for the transfer hook +#[derive(SplDiscriminate)] +#[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")] +pub struct InitializeExtraAccountMetasInstruction; impl TransferHookInstruction { /// Unpacks a byte buffer into a [TransferHookInstruction](enum.TransferHookInstruction.html). pub fn unpack(input: &[u8]) -> Result { - const EXECUTE_DISCRIMINATOR_SLICE: &[u8] = ExecuteInstruction::TLV_DISCRIMINATOR.as_slice(); - if input.len() < Discriminator::LENGTH { + if input.len() < ArrayDiscriminator::LENGTH { return Err(ProgramError::InvalidInstructionData); } - let (discriminator, rest) = input.split_at(Discriminator::LENGTH); + let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH); Ok(match discriminator { - EXECUTE_DISCRIMINATOR_SLICE => { + ExecuteInstruction::SPL_DISCRIMINATOR_SLICE => { let amount = rest .get(..8) .and_then(|slice| slice.try_into().ok()) @@ -73,7 +72,9 @@ impl TransferHookInstruction { .ok_or(ProgramError::InvalidInstructionData)?; Self::Execute { amount } } - INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR => Self::InitializeExtraAccountMetas, + InitializeExtraAccountMetasInstruction::SPL_DISCRIMINATOR_SLICE => { + Self::InitializeExtraAccountMetas + } _ => return Err(ProgramError::InvalidInstructionData), }) } @@ -83,11 +84,13 @@ impl TransferHookInstruction { let mut buf = vec![]; match self { Self::Execute { amount } => { - buf.extend_from_slice(ExecuteInstruction::TLV_DISCRIMINATOR.as_slice()); + buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE); buf.extend_from_slice(&amount.to_le_bytes()); } Self::InitializeExtraAccountMetas => { - buf.extend_from_slice(INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR); + buf.extend_from_slice( + InitializeExtraAccountMetasInstruction::SPL_DISCRIMINATOR_SLICE, + ); } }; buf @@ -180,10 +183,10 @@ mod test { let amount = 111_111_111; let check = TransferHookInstruction::Execute { amount }; let packed = check.pack(); - // Please use ExecuteInstruction::TLV_DISCRIMINATOR in your program, the + // Please use ExecuteInstruction::SPL_DISCRIMINATOR in your program, the // following is just for test purposes let preimage = hash::hashv(&[format!("{NAMESPACE}:execute").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; let mut expect = vec![]; expect.extend_from_slice(discriminator.as_ref()); expect.extend_from_slice(&amount.to_le_bytes()); @@ -200,7 +203,7 @@ mod test { // the following is just for test purposes let preimage = hash::hashv(&[format!("{NAMESPACE}:initialize-extra-account-metas").as_bytes()]); - let discriminator = &preimage.as_ref()[..Discriminator::LENGTH]; + let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; let mut expect = vec![]; expect.extend_from_slice(discriminator.as_ref()); assert_eq!(packed, expect);