Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 307d10e

Browse files
author
Joe C
authored
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()
1 parent 7c3c753 commit 307d10e

File tree

25 files changed

+616
-261
lines changed

25 files changed

+616
-261
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ members = [
2222
"governance/tools",
2323
"governance/chat/program",
2424
"instruction-padding/program",
25-
"libraries/math",
25+
"libraries/discriminator",
2626
"libraries/concurrent-merkle-tree",
27+
"libraries/math",
2728
"libraries/merkle-tree-reference",
2829
"libraries/program-error",
2930
"libraries/tlv-account-resolution",

libraries/discriminator/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "spl-discriminator"
3+
version = "0.1.0"
4+
description = "Solana Program Library 8-Byte Discriminator Management"
5+
authors = ["Solana Labs Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[dependencies]
11+
bytemuck = { version = "1.13.1", features = ["derive"] }
12+
solana-program = "1.14.12"
13+
spl-discriminator-derive = { version = "0.1.0", path = "./derive" }
14+
15+
[lib]
16+
crate-type = ["cdylib", "lib"]
17+
18+
[package.metadata.docs.rs]
19+
targets = ["x86_64-unknown-linux-gnu"]

libraries/discriminator/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPL Discriminator
2+
3+
This library allows for easy management of 8-byte discriminators.
4+
5+
### The `ArrayDiscriminator` Struct
6+
7+
With this crate, you can leverage the `ArrayDiscriminator` type to manage an 8-byte discriminator for generic purposes.
8+
9+
```rust
10+
let my_discriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]);
11+
```
12+
13+
The `new(..)` function is also a **constant function**, so you can use `ArrayDiscriminator` in constants as well.
14+
15+
```rust
16+
const MY_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([8, 5, 1, 56, 10, 53, 9, 198]);
17+
```
18+
19+
The `ArrayDiscriminator` struct also offers another constant function `as_slice(&self)`, so you can use `as_slice()` in constants as well.
20+
21+
```rust
22+
const MY_DISCRIMINATOR_SLICE: &[u8] = MY_DISCRIMINATOR.as_slice();
23+
```
24+
25+
### The `SplDiscriminate` Trait
26+
27+
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.
28+
29+
```rust
30+
/// A trait for managing 8-byte discriminators in a slab of bytes
31+
pub trait SplDiscriminate {
32+
/// The 8-byte discriminator as a `[u8; 8]`
33+
const SPL_DISCRIMINATOR: ArrayDiscriminator;
34+
/// The 8-byte discriminator as a slice (`&[u8]`)
35+
const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice();
36+
}
37+
```
38+
39+
### The `SplDiscriminate` Derive Macro
40+
41+
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.
42+
43+
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!
44+
45+
```rust
46+
#[derive(SplDiscriminate)] // Implements `SplDiscriminate` for your struct/enum using your declared string literal namespace
47+
#[discriminator_namespace("some_discriminator_namespace")]
48+
pub struct MyInstruction1 {
49+
arg1: String,
50+
arg2: u8,
51+
}
52+
53+
let my_discriminator: ArrayDiscriminator = MyInstruction1::SPL_DISCRIMINATOR;
54+
let my_discriminator_slice: &[u8] = MyInstruction1::SPL_DISCRIMINATOR_SLICE;
55+
```
56+
57+
Note: the 8-byte discriminator derived using the macro is always the **first 8 bytes** of the resulting hashed bytes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "spl-discriminator-derive"
3+
version = "0.1.0"
4+
description = "Derive macro library for the `spl-discriminator` library"
5+
authors = ["Solana Labs Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[lib]
11+
proc-macro = true
12+
13+
[dependencies]
14+
quote = "1.0"
15+
spl-discriminator-syn = { version = "0.1.0", path = "../syn" }
16+
syn = { version = "2.0", features = ["full"] }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Derive macro library for the `spl-discriminator` library
2+
3+
#![deny(missing_docs)]
4+
#![cfg_attr(not(test), forbid(unsafe_code))]
5+
6+
extern crate proc_macro;
7+
8+
use proc_macro::TokenStream;
9+
use quote::ToTokens;
10+
use spl_discriminator_syn::SplDiscriminateBuilder;
11+
use syn::parse_macro_input;
12+
13+
/// Derive macro library to implement the `SplDiscriminate` trait
14+
/// on an enum or struct
15+
#[proc_macro_derive(SplDiscriminate, attributes(discriminator_hash_input))]
16+
pub fn spl_discriminator(input: TokenStream) -> TokenStream {
17+
parse_macro_input!(input as SplDiscriminateBuilder)
18+
.to_token_stream()
19+
.into()
20+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! The traits and types used to create a discriminator for a type
2+
3+
use {
4+
bytemuck::{Pod, Zeroable},
5+
solana_program::{hash, program_error::ProgramError},
6+
};
7+
8+
/// A trait for managing 8-byte discriminators in a slab of bytes
9+
pub trait SplDiscriminate {
10+
/// The 8-byte discriminator as a `[u8; 8]`
11+
const SPL_DISCRIMINATOR: ArrayDiscriminator;
12+
/// The 8-byte discriminator as a slice (`&[u8]`)
13+
const SPL_DISCRIMINATOR_SLICE: &'static [u8] = Self::SPL_DISCRIMINATOR.as_slice();
14+
}
15+
16+
/// Array Discriminator type
17+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
18+
#[repr(transparent)]
19+
pub struct ArrayDiscriminator([u8; ArrayDiscriminator::LENGTH]);
20+
impl ArrayDiscriminator {
21+
/// Size for discriminator in account data
22+
pub const LENGTH: usize = 8;
23+
/// Uninitialized variant of a discriminator
24+
pub const UNINITIALIZED: Self = Self::new([0; Self::LENGTH]);
25+
/// Creates a discriminator from an array
26+
pub const fn new(value: [u8; Self::LENGTH]) -> Self {
27+
Self(value)
28+
}
29+
/// Get the array as a const slice
30+
pub const fn as_slice(&self) -> &[u8] {
31+
self.0.as_slice()
32+
}
33+
/// Creates a new `ArrayDiscriminator` from some hash input string literal
34+
pub fn new_with_hash_input(hash_input: &str) -> Self {
35+
let hash_bytes = hash::hashv(&[hash_input.as_bytes()]).to_bytes();
36+
let mut discriminator_bytes = [0u8; 8];
37+
discriminator_bytes.copy_from_slice(&hash_bytes[..8]);
38+
Self(discriminator_bytes)
39+
}
40+
}
41+
impl AsRef<[u8]> for ArrayDiscriminator {
42+
fn as_ref(&self) -> &[u8] {
43+
&self.0[..]
44+
}
45+
}
46+
impl AsRef<[u8; ArrayDiscriminator::LENGTH]> for ArrayDiscriminator {
47+
fn as_ref(&self) -> &[u8; ArrayDiscriminator::LENGTH] {
48+
&self.0
49+
}
50+
}
51+
impl From<u64> for ArrayDiscriminator {
52+
fn from(from: u64) -> Self {
53+
Self(from.to_le_bytes())
54+
}
55+
}
56+
impl From<[u8; Self::LENGTH]> for ArrayDiscriminator {
57+
fn from(from: [u8; Self::LENGTH]) -> Self {
58+
Self(from)
59+
}
60+
}
61+
impl TryFrom<&[u8]> for ArrayDiscriminator {
62+
type Error = ProgramError;
63+
fn try_from(a: &[u8]) -> Result<Self, Self::Error> {
64+
<[u8; Self::LENGTH]>::try_from(a)
65+
.map(Self::from)
66+
.map_err(|_| ProgramError::InvalidAccountData)
67+
}
68+
}

libraries/discriminator/src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//! Crate defining a discriminator type, which creates a set of bytes
2+
//! meant to be unique for instructions or struct types
3+
4+
#![deny(missing_docs)]
5+
#![cfg_attr(not(test), forbid(unsafe_code))]
6+
7+
extern crate self as spl_discriminator;
8+
9+
/// Exports the discriminator module
10+
pub mod discriminator;
11+
12+
// Export for downstream
13+
pub use {
14+
discriminator::{ArrayDiscriminator, SplDiscriminate},
15+
spl_discriminator_derive::SplDiscriminate,
16+
};
17+
18+
#[cfg(test)]
19+
mod tests {
20+
use {super::*, crate::discriminator::ArrayDiscriminator};
21+
22+
#[allow(dead_code)]
23+
#[derive(SplDiscriminate)]
24+
#[discriminator_hash_input("some_discriminator_hash_input")]
25+
pub struct MyInstruction1 {
26+
arg1: String,
27+
arg2: u8,
28+
}
29+
30+
#[allow(dead_code)]
31+
#[derive(SplDiscriminate)]
32+
#[discriminator_hash_input("yet_another_discriminator_hash_input")]
33+
pub struct MyInstruction2 {
34+
arg1: u64,
35+
}
36+
37+
#[allow(dead_code)]
38+
#[derive(SplDiscriminate)]
39+
#[discriminator_hash_input("global:my_instruction_3")]
40+
pub enum MyInstruction3 {
41+
One,
42+
Two,
43+
Three,
44+
}
45+
46+
fn assert_discriminator<T: spl_discriminator::discriminator::SplDiscriminate>(
47+
hash_input: &str,
48+
) {
49+
let discriminator = build_discriminator(hash_input);
50+
assert_eq!(T::SPL_DISCRIMINATOR, discriminator);
51+
assert_eq!(T::SPL_DISCRIMINATOR_SLICE, discriminator.as_slice());
52+
}
53+
54+
fn build_discriminator(hash_input: &str) -> ArrayDiscriminator {
55+
let preimage = solana_program::hash::hashv(&[hash_input.as_bytes()]);
56+
let mut bytes = [0u8; 8];
57+
bytes.copy_from_slice(&preimage.to_bytes()[..8]);
58+
ArrayDiscriminator::new(bytes)
59+
}
60+
61+
#[test]
62+
fn test_discrminators() {
63+
assert_discriminator::<MyInstruction1>("some_discriminator_hash_input");
64+
assert_discriminator::<MyInstruction2>("yet_another_discriminator_hash_input");
65+
assert_discriminator::<MyInstruction3>("global:my_instruction_3");
66+
let runtime_discrim = ArrayDiscriminator::new_with_hash_input("my_new_hash_input");
67+
assert_eq!(runtime_discrim, build_discriminator("my_new_hash_input"),);
68+
}
69+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "spl-discriminator-syn"
3+
version = "0.1.0"
4+
description = "Token parsing and generating library for the `spl-discriminator` library"
5+
authors = ["Solana Labs Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[dependencies]
11+
proc-macro2 = "1.0"
12+
quote = "1.0"
13+
solana-program = "1.14.12"
14+
syn = { version = "2.0", features = ["full"] }
15+
thiserror = "1.0"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Error types for the `hash_input` parser
2+
3+
/// Error types for the `hash_input` parser
4+
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
5+
pub enum SplDiscriminateError {
6+
/// Discriminator hash_input attribute not provided
7+
#[error("Discriminator `hash_input` attribute not provided")]
8+
HashInputAttributeNotProvided,
9+
/// Error parsing discriminator hash_input attribute
10+
#[error("Error parsing discriminator `hash_input` attribute")]
11+
HashInputAttributeParseError,
12+
}

0 commit comments

Comments
 (0)