forked from solana-labs/solana-program-library
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
spl discriminator
crate (solana-labs#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()
- Loading branch information
Joe C
authored
Jun 22, 2023
1 parent
7c3c753
commit 307d10e
Showing
25 changed files
with
616 additions
and
261 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "spl-discriminator" | ||
version = "0.1.0" | ||
description = "Solana Program Library 8-Byte Discriminator Management" | ||
authors = ["Solana Labs Maintainers <[email protected]>"] | ||
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <[email protected]>"] | ||
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<u64> 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<Self, Self::Error> { | ||
<[u8; Self::LENGTH]>::try_from(a) | ||
.map(Self::from) | ||
.map_err(|_| ProgramError::InvalidAccountData) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T: spl_discriminator::discriminator::SplDiscriminate>( | ||
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::<MyInstruction1>("some_discriminator_hash_input"); | ||
assert_discriminator::<MyInstruction2>("yet_another_discriminator_hash_input"); | ||
assert_discriminator::<MyInstruction3>("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"),); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <[email protected]>"] | ||
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} |
Oops, something went wrong.